# frozen_string_literal: true RSpec.describe FiniteMachine, '#target' do it "allows to target external object" do stub_const("Car", Class.new do attr_accessor :reverse_lights def turn_reverse_lights_off @reverse_lights = false end def turn_reverse_lights_on @reverse_lights = true end def reverse_lights? @reverse_lights ||= false end def engine @engine ||= FiniteMachine.new(self) do initial :neutral event :forward, [:reverse, :neutral] => :one event :shift, :one => :two event :shift, :two => :one event :back, [:neutral, :one] => :reverse on_enter :reverse do |event| target.turn_reverse_lights_on end on_exit :reverse do |event| target.turn_reverse_lights_off end end end end) car = Car.new expect(car.reverse_lights?).to be(false) expect(car.engine.current).to eql(:neutral) car.engine.back expect(car.engine.current).to eql(:reverse) expect(car.reverse_lights?).to be(true) car.engine.forward expect(car.engine.current).to eql(:one) expect(car.reverse_lights?).to be(false) end it "propagates method call" do fsm = FiniteMachine.new do initial :green event :slow, :green => :yellow on_enter_yellow do |event| uknown_method end end expect(fsm.current).to eql(:green) expect { fsm.slow }.to raise_error(StandardError) end it "references machine methods inside callback" do called = [] fsm = FiniteMachine.new do initial :green event :slow, :green => :yellow event :stop, :yellow => :red event :ready, :red => :yellow event :go, :yellow => :green on_enter_yellow do |event| stop(:now) end on_enter_red do |event, param| called << "#{event.from} #{param}" end end expect(fsm.current).to eql(:green) fsm.slow expect(fsm.current).to eql(:red) expect(called).to eql(['yellow now']) end it "allows context methods take precedence over machine ones" do stub_const("Car", Class.new do attr_accessor :reverse_lights attr_accessor :called def turn_reverse_lights_off @reverse_lights = false end def turn_reverse_lights_on @reverse_lights = true end def reverse_lights? @reverse_lights ||= false end def engine self.called ||= [] @engine ||= FiniteMachine.new(self) do initial :neutral event :forward, [:reverse, :neutral] => :one event :shift, :one => :two event :shift, :two => :one event :back, [:neutral, :one] => :reverse on_enter :reverse do |event| target.called << 'on_enter_reverse' target.turn_reverse_lights_on forward('Piotr!') end on_before :forward do |event, name| target.called << "on_enter_forward with #{name}" end end end end) car = Car.new expect(car.reverse_lights?).to be(false) expect(car.engine.current).to eql(:neutral) car.engine.back expect(car.engine.current).to eql(:one) expect(car.called).to eql([ 'on_enter_reverse', 'on_enter_forward with Piotr!' ]) end it "allows to access target inside the callback" do context = double(:context) called = nil fsm = FiniteMachine.new(context) do initial :green event :slow, :green => :yellow event :stop, :yellow => :red on_enter_yellow do |event| called = target end end expect(fsm.current).to eql(:green) fsm.slow expect(called).to eq(context) end it "allows to differentiate between same named methods" do called = [] stub_const("Car", Class.new do def initialize(called) @called = called end def save @called << 'car save called' end end) car = Car.new(called) fsm = FiniteMachine.new(car) do initial :unsaved event :validate, :unsaved => :valid event :save, :valid => :saved on_enter :valid do |event| target.save save end on_after :save do |event| called << 'event save called' end end expect(fsm.current).to eql(:unsaved) fsm.validate expect(fsm.current).to eql(:saved) expect(called).to eq([ 'car save called', 'event save called' ]) end it "handles targets responding to :to_hash message" do stub_const("Serializer", Class.new do def initialize(data) @data = data end def write(new_data) @data.merge!(new_data) end def to_hash @data end alias to_h to_hash end) model = Serializer.new({a: 1, b: 2}) fsm = FiniteMachine.new(model) do initial :a event :serialize, :a => :b on_after :serialize do |event| target.write(c: 3) end end fsm.serialize expect(model.to_h).to include({a: 1, b: 2, c: 3}) end end