From 475985f282834c3d26fa38f36410ba722c469f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorsten=20Bo=CC=88ttger?= Date: Wed, 28 Nov 2012 22:42:41 +1300 Subject: [PATCH] started to clean up the specs --- lib/aasm/aasm.rb | 11 +- lib/aasm/state_machine.rb | 7 +- spec/models/argument.rb | 11 + spec/spec_helpers/models_spec_helper.rb | 12 - spec/unit/aasm_spec.rb | 288 ++++++------------------ spec/unit/callbacks_new_dsl_spec.rb | 16 ++ spec/unit/callbacks_old_dsl_spec.rb | 80 +++++++ spec/unit/event_spec.rb | 43 ++++ spec/unit/inspection_spec.rb | 7 - 9 files changed, 227 insertions(+), 248 deletions(-) create mode 100644 spec/models/argument.rb delete mode 100644 spec/unit/inspection_spec.rb diff --git a/lib/aasm/aasm.rb b/lib/aasm/aasm.rb index 685d6aa..f37cc88 100644 --- a/lib/aasm/aasm.rb +++ b/lib/aasm/aasm.rb @@ -2,15 +2,14 @@ module AASM def self.included(base) #:nodoc: base.extend AASM::ClassMethods - - unless AASM::StateMachine[base] - AASM::StateMachine[base] = AASM::StateMachine.new('') - end + AASM::StateMachine[base] ||= AASM::StateMachine.new('') AASM::Persistence.set_persistence(base) super end module ClassMethods + + # make sure inheritance (aka subclassing) works with AASM def inherited(klass) AASM::StateMachine[klass] = AASM::StateMachine[self].clone super @@ -18,13 +17,13 @@ module AASM def aasm(options={}, &block) @aasm ||= AASM::Base.new(self, options) - @aasm.instance_eval(&block) if block + @aasm.instance_eval(&block) if block # new DSL @aasm end def aasm_initial_state(set_state=nil) if set_state - # deprecated + # deprecated way to set the value AASM::StateMachine[self].initial_state = set_state else AASM::StateMachine[self].initial_state diff --git a/lib/aasm/state_machine.rb b/lib/aasm/state_machine.rb index 667e337..18531e0 100644 --- a/lib/aasm/state_machine.rb +++ b/lib/aasm/state_machine.rb @@ -1,5 +1,7 @@ module AASM class StateMachine + + # the following two methods provide the storage of all state machines def self.[](clazz) (@machines ||= {})[clazz.to_s] end @@ -11,6 +13,7 @@ module AASM attr_accessor :states, :events, :initial_state, :config attr_reader :name + # QUESTION: what's the name for? [alto, 2012-11-28] def initialize(name) @name = name @initial_state = nil @@ -19,6 +22,7 @@ module AASM @config = OpenStruct.new end + # called internally by Ruby 1.9 after clone() def initialize_copy(orig) super @states = @states.dup @@ -28,5 +32,6 @@ module AASM def create_state(name, options) @states << AASM::SupportingClasses::State.new(name, options) unless @states.include?(name) end - end + + end # StateMachine end # AASM diff --git a/spec/models/argument.rb b/spec/models/argument.rb new file mode 100644 index 0000000..da4aaea --- /dev/null +++ b/spec/models/argument.rb @@ -0,0 +1,11 @@ +class Argument + include AASM + aasm do + state :invalid, :initial => true + state :valid + + event :valid do + transitions :to => :valid, :from => [:invalid] + end + end +end diff --git a/spec/spec_helpers/models_spec_helper.rb b/spec/spec_helpers/models_spec_helper.rb index f7188df..f7d0db8 100644 --- a/spec/spec_helpers/models_spec_helper.rb +++ b/spec/spec_helpers/models_spec_helper.rb @@ -64,18 +64,6 @@ class Banker def rich?; self.balance >= RICH; end end -class Argument - include AASM - aasm do - state :invalid, :initial => true - state :valid - - event :valid do - transitions :to => :valid, :from => [:invalid] - end - end -end - class ThisNameBetterNotBeInUse include AASM diff --git a/spec/unit/aasm_spec.rb b/spec/unit/aasm_spec.rb index 9001623..2d9d46b 100644 --- a/spec/unit/aasm_spec.rb +++ b/spec/unit/aasm_spec.rb @@ -1,19 +1,42 @@ -require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper')) +require 'spec_helper' -describe AASM, '- class level definitions' do - it 'should define a class level methods on its including class' do - Foo.should respond_to(:aasm_initial_state) - Foo.should respond_to(:aasm_state) - Foo.should respond_to(:aasm_event) - Foo.should respond_to(:aasm_states) - Foo.should respond_to(:aasm_states_for_select) - Foo.should respond_to(:aasm_events) - Foo.should respond_to(:aasm_from_states_for_state) +# all of these should be tested per case +# describe AASM, '- class level definitions' do +# it 'should define a class level methods on its including class' do +# Foo.should respond_to(:aasm_initial_state) +# Foo.should respond_to(:aasm_state) +# Foo.should respond_to(:aasm_event) +# Foo.should respond_to(:aasm_states) +# Foo.should respond_to(:aasm_states_for_select) +# Foo.should respond_to(:aasm_events) +# Foo.should respond_to(:aasm_from_states_for_state) +# end +# end + +describe 'inspection for common cases' do + it 'should support the old DSL' do + Foo.aasm_states.should include(:open) + Foo.aasm_states.should include(:closed) + Foo.aasm_initial_state.should == :open + Foo.aasm_events.should include(:close) + Foo.aasm_events.should include(:null) + end + + it 'should support the new DSL' do + Foo.aasm.states.should include(:open) + Foo.aasm.states.should include(:closed) + # Foo.aasm.initial_state.should == :open # does not work yet + Foo.aasm.events.should include(:close) + Foo.aasm.events.should include(:null) + end + + it 'should list states in the order they have been defined' do + Conversation.aasm.states.should == [:needs_attention, :read, :closed, :awaiting_response, :junk] end end -describe "naming" do - it "work for valid" do +describe "special cases" do + it "should support valid a state name" do Argument.aasm_states.should include(:invalid) Argument.aasm_states.should include(:valid) @@ -27,26 +50,30 @@ describe "naming" do end end -describe AASM, '- subclassing' do +describe 'subclassing' do it 'should have the parent states' do Foo.aasm_states.each do |state| FooTwo.aasm_states.should include(state) end + Baz.aasm_states.should == Bar.aasm_states end it 'should not add the child states to the parent machine' do Foo.aasm_states.should_not include(:foo) end + + it "should have the same events as its parent" do + Baz.aasm_events.should == Bar.aasm_events + end end - -describe AASM, '- aasm_states_for_select' do +describe :aasm_states_for_select do it "should return a select friendly array of states" do Foo.aasm_states_for_select.should == [['Open', 'open'], ['Closed', 'closed']] end end -describe "aasm_from_states_for_state" do +describe :aasm_from_states_for_state do it "should return all from states for a state" do froms = AuthMachine.aasm_from_states_for_state(:active) [:pending, :passive, :suspended].each {|from| froms.should include(from)} @@ -58,40 +85,35 @@ describe "aasm_from_states_for_state" do end end -describe AASM, '- instance level definitions' do - before(:each) do - @foo = Foo.new - end +describe 'instance methods' do + let(:foo) {Foo.new} it 'should define a state querying instance method on including class' do - @foo.should respond_to(:open?) + foo.should respond_to(:open?) + foo.should be_open end - it 'should define an event! inance method' do - @foo.should respond_to(:close!) + it 'should define an event! instance method' do + foo.should respond_to(:close!) + foo.close! + foo.should be_closed end end describe AASM, '- initial states' do - before(:each) do - @foo = Foo.new - @bar = Bar.new - end + let(:foo) {Foo.new} + let(:bar) {Bar.new} it 'should set the initial state' do - @foo.aasm_current_state.should == :open - end - - it '#open? should be initially true' do - @foo.open?.should be_true - end - - it '#closed? should be initially false' do - @foo.closed?.should be_false + foo.aasm_current_state.should == :open + # foo.aasm.current_state.should == :open # not yet supported + foo.should be_open + foo.should_not be_closed end it 'should use the first state defined if no initial state is given' do - @bar.aasm_current_state.should == :read + bar.aasm_current_state.should == :read + # bar.aasm.current_state.should == :read # not yet supported end it 'should determine initial state from the Proc results' do @@ -100,202 +122,24 @@ describe AASM, '- initial states' do end end -describe AASM, 'success callbacks' do - it 'should call the success callback if one was provided' do - foo = Foo.new - foo.should_receive(:success_callback) - foo.close! - end - -end - -describe AASM, '- event firing without persistence' do +describe 'event firing without persistence' do it 'should attempt to persist if aasm_write_state is defined' do foo = Foo.new - - def foo.aasm_write_state - end + def foo.aasm_write_state; end foo.should_receive(:aasm_write_state_without_persistence).twice - foo.close end end -describe AASM, '- getting events for a state' do - it '#aasm_events_for_current_state should use current state' do - foo = Foo.new - foo.should_receive(:aasm_current_state) - foo.aasm_events_for_current_state - end - - it '#aasm_events_for_current_state should use aasm_events_for_state' do - foo = Foo.new - foo.stub!(:aasm_current_state).and_return(:foo) - foo.should_receive(:aasm_events_for_state).with(:foo) - foo.aasm_events_for_current_state - end -end - -describe AASM, '- event callbacks' do - describe "with an error callback defined" do - before do - class Foo - aasm_event :safe_close, :success => :success_callback, :error => :error_callback do - transitions :to => :closed, :from => [:open] - end - end - - @foo = Foo.new - end - - it "should run error_callback if an exception is raised and error_callback defined" do - def @foo.error_callback(e) - end - @foo.stub!(:enter).and_raise(e=StandardError.new) - @foo.should_receive(:error_callback).with(e) - @foo.safe_close! - end - - it "should raise NoMethodError if exceptionis raised and error_callback is declared but not defined" do - @foo.stub!(:enter).and_raise(StandardError) - lambda{@foo.safe_close!}.should raise_error(NoMethodError) - end - - it "should propagate an error if no error callback is declared" do - @foo.stub!(:enter).and_raise("Cannot enter safe") - lambda{@foo.close!}.should raise_error(StandardError, "Cannot enter safe") - end - end - - describe "with aasm_event_fired defined" do - before do - @foo = Foo.new - def @foo.aasm_event_fired(event, from, to) - end - end - - it 'should call it for successful bang fire' do - @foo.should_receive(:aasm_event_fired).with(:close, :open, :closed) - @foo.close! - end - - it 'should call it for successful non-bang fire' do - @foo.should_receive(:aasm_event_fired) - @foo.close - end - - it 'should not call it for failing bang fire' do - @foo.stub!(:aasm_set_current_state_with_persistence).and_return(false) - @foo.should_not_receive(:aasm_event_fired) - @foo.close! - end - end - - describe "with aasm_event_failed defined" do - before do - @foo = Foo.new - def @foo.aasm_event_failed(event, from) - end - end - - it 'should call it when transition failed for bang fire' do - @foo.should_receive(:aasm_event_failed).with(:null, :open) - lambda {@foo.null!}.should raise_error(AASM::InvalidTransition) - end - - it 'should call it when transition failed for non-bang fire' do - @foo.should_receive(:aasm_event_failed).with(:null, :open) - lambda {@foo.null}.should raise_error(AASM::InvalidTransition) - end - - it 'should not call it if persist fails for bang fire' do - @foo.stub!(:aasm_set_current_state_with_persistence).and_return(false) - @foo.should_receive(:aasm_event_failed) - @foo.close! - end - end -end - -describe AASM, '- state actions' do - it "should call enter when entering state" do - foo = Foo.new - foo.should_receive(:enter) - - foo.close - end - - it "should call exit when exiting state" do - foo = Foo.new - foo.should_receive(:exit) +describe :aasm_events_for_current_state do + let(:foo) {Foo.new} + it 'work' do + foo.aasm_events_for_current_state.should == [:close, :null] foo.close + foo.aasm_events_for_current_state.should be_empty end end -describe Baz do - it "should have the same states as it's parent" do - Baz.aasm_states.should == Bar.aasm_states - end - - it "should have the same events as it's parent" do - Baz.aasm_events.should == Bar.aasm_events - end -end - - - -describe ChetanPatil do - it 'should transition to specified next state (sleeping to showering)' do - cp = ChetanPatil.new - cp.wakeup! :showering - - cp.aasm_current_state.should == :showering - end - - it 'should transition to specified next state (sleeping to working)' do - cp = ChetanPatil.new - cp.wakeup! :working - - cp.aasm_current_state.should == :working - end - - it 'should transition to default (first or showering) state' do - cp = ChetanPatil.new - cp.wakeup! - - cp.aasm_current_state.should == :showering - end - - it 'should transition to default state when on_transition invoked' do - cp = ChetanPatil.new - cp.dress!(nil, 'purple', 'dressy') - - cp.aasm_current_state.should == :working - end - - it 'should call on_transition method with args' do - cp = ChetanPatil.new - cp.wakeup! :showering - - cp.should_receive(:wear_clothes).with('blue', 'jeans') - cp.dress! :working, 'blue', 'jeans' - end - - it 'should call on_transition proc' do - cp = ChetanPatil.new - cp.wakeup! :showering - - cp.should_receive(:wear_clothes).with('purple', 'slacks') - cp.dress!(:dating, 'purple', 'slacks') - end - - it 'should call on_transition with an array of methods' do - cp = ChetanPatil.new - cp.wakeup! :showering - cp.should_receive(:condition_hair) - cp.should_receive(:fix_hair) - cp.dress!(:prettying_up) - end -end diff --git a/spec/unit/callbacks_new_dsl_spec.rb b/spec/unit/callbacks_new_dsl_spec.rb index 4972036..8263e13 100644 --- a/spec/unit/callbacks_new_dsl_spec.rb +++ b/spec/unit/callbacks_new_dsl_spec.rb @@ -31,3 +31,19 @@ describe 'callbacks for the new DSL' do @callback.open! end end + +describe AASM, '- state actions' do + it "should call enter when entering state" do + foo = Foo.new + foo.should_receive(:enter) + + foo.close + end + + it "should call exit when exiting state" do + foo = Foo.new + foo.should_receive(:exit) + + foo.close + end +end diff --git a/spec/unit/callbacks_old_dsl_spec.rb b/spec/unit/callbacks_old_dsl_spec.rb index d93ea3e..9db7d24 100644 --- a/spec/unit/callbacks_old_dsl_spec.rb +++ b/spec/unit/callbacks_old_dsl_spec.rb @@ -31,3 +31,83 @@ describe 'callbacks for the old DSL' do @callback.open! end end + +describe AASM, '- event callbacks' do + describe "with an error callback defined" do + before do + class Foo + aasm_event :safe_close, :success => :success_callback, :error => :error_callback do + transitions :to => :closed, :from => [:open] + end + end + + @foo = Foo.new + end + + it "should run error_callback if an exception is raised and error_callback defined" do + def @foo.error_callback(e) + end + @foo.stub!(:enter).and_raise(e=StandardError.new) + @foo.should_receive(:error_callback).with(e) + @foo.safe_close! + end + + it "should raise NoMethodError if exceptionis raised and error_callback is declared but not defined" do + @foo.stub!(:enter).and_raise(StandardError) + lambda{@foo.safe_close!}.should raise_error(NoMethodError) + end + + it "should propagate an error if no error callback is declared" do + @foo.stub!(:enter).and_raise("Cannot enter safe") + lambda{@foo.close!}.should raise_error(StandardError, "Cannot enter safe") + end + end + + describe "with aasm_event_fired defined" do + before do + @foo = Foo.new + def @foo.aasm_event_fired(event, from, to) + end + end + + it 'should call it for successful bang fire' do + @foo.should_receive(:aasm_event_fired).with(:close, :open, :closed) + @foo.close! + end + + it 'should call it for successful non-bang fire' do + @foo.should_receive(:aasm_event_fired) + @foo.close + end + + it 'should not call it for failing bang fire' do + @foo.stub!(:aasm_set_current_state_with_persistence).and_return(false) + @foo.should_not_receive(:aasm_event_fired) + @foo.close! + end + end + + describe "with aasm_event_failed defined" do + before do + @foo = Foo.new + def @foo.aasm_event_failed(event, from) + end + end + + it 'should call it when transition failed for bang fire' do + @foo.should_receive(:aasm_event_failed).with(:null, :open) + lambda {@foo.null!}.should raise_error(AASM::InvalidTransition) + end + + it 'should call it when transition failed for non-bang fire' do + @foo.should_receive(:aasm_event_failed).with(:null, :open) + lambda {@foo.null}.should raise_error(AASM::InvalidTransition) + end + + it 'should not call it if persist fails for bang fire' do + @foo.stub!(:aasm_set_current_state_with_persistence).and_return(false) + @foo.should_receive(:aasm_event_failed) + @foo.close! + end + end +end diff --git a/spec/unit/event_spec.rb b/spec/unit/event_spec.rb index cec45da..251a942 100644 --- a/spec/unit/event_spec.rb +++ b/spec/unit/event_spec.rb @@ -148,3 +148,46 @@ describe 'executing the success callback' do model.with_proc! end end + +describe 'parametrised events' do + let(:cp) {ChetanPatil.new} + + it 'should transition to specified next state (sleeping to showering)' do + cp.wakeup!(:showering) + cp.aasm_current_state.should == :showering + end + + it 'should transition to specified next state (sleeping to working)' do + cp.wakeup!(:working) + cp.aasm_current_state.should == :working + end + + it 'should transition to default (first or showering) state' do + cp.wakeup! + cp.aasm_current_state.should == :showering + end + + it 'should transition to default state when on_transition invoked' do + cp.dress!(nil, 'purple', 'dressy') + cp.aasm_current_state.should == :working + end + + it 'should call on_transition method with args' do + cp.wakeup!(:showering) + cp.should_receive(:wear_clothes).with('blue', 'jeans') + cp.dress!(:working, 'blue', 'jeans') + end + + it 'should call on_transition proc' do + cp.wakeup!(:showering) + cp.should_receive(:wear_clothes).with('purple', 'slacks') + cp.dress!(:dating, 'purple', 'slacks') + end + + it 'should call on_transition with an array of methods' do + cp.wakeup!(:showering) + cp.should_receive(:condition_hair) + cp.should_receive(:fix_hair) + cp.dress!(:prettying_up) + end +end diff --git a/spec/unit/inspection_spec.rb b/spec/unit/inspection_spec.rb deleted file mode 100644 index 2c0559c..0000000 --- a/spec/unit/inspection_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'spec_helper' - -describe 'inspecting AASM' do - it 'should support listing all states in the order they have been defined' do - Conversation.aasm_states.should == [:needs_attention, :read, :closed, :awaiting_response, :junk] - end -end