This commit is contained in:
Xes
2025-08-14 22:41:49 +02:00
parent 2de81ccc46
commit 8ce45119b6
39774 changed files with 4309466 additions and 0 deletions

View File

@@ -0,0 +1,163 @@
<?php
namespace spec\SM\Callback;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use SM\Event\TransitionEvent;
use SM\StateMachine\StateMachineInterface;
class CallbackSpec extends ObjectBehavior
{
protected $specs = array();
protected $callable;
protected $sm;
function let(StateMachineInterface $sm)
{
$sm->getState()->willReturn('checkout');
$this->beConstructedWith($this->specs, $this->callable);
}
function it_is_initializable()
{
$this->shouldHaveType('SM\Callback\Callback');
}
function it_satisfies_simple_on(TransitionEvent $event)
{
$specs = array('on' => 'tested-transition');
$this->beConstructedWith($specs, $this->callable);
$event->getConfig()->willReturn($this->getConfig(array('dummy'), 'dummy'));
$event->getTransition()->willReturn('tested-transition');
$event->getState()->willReturn('dummy');
$this->isSatisfiedBy($event)->shouldReturn(true);
}
function it_doesnt_satisfies_simple_on(TransitionEvent $event)
{
$specs = array('on' => 'tested-transition');
$this->beConstructedWith($specs, $this->callable);
$event->getConfig()->willReturn($this->getConfig(array('dummy'), 'dummy'));
$event->getTransition()->willReturn('tested-transition-not-matching');
$this->isSatisfiedBy($event)->shouldReturn(false);
}
function it_satisfies_simple_from(TransitionEvent $event)
{
$specs = array('from' => 'tested-state');
$this->beConstructedWith($specs, $this->callable);
$event->getConfig()->willReturn($this->getConfig(array('tested-state'), 'dummy'));
$event->getTransition()->willReturn('dummy');
$event->getState()->willReturn('tested-state');
$this->isSatisfiedBy($event)->shouldReturn(true);
}
function it_doesnt_satisfies_simple_from(TransitionEvent $event)
{
$specs = array('from' => 'tested-state');
$this->beConstructedWith($specs, $this->callable);
$event->getConfig()->willReturn($this->getConfig(array('tested-state-not-matching'), 'dummy'));
$event->getTransition()->willReturn('dummy');
$event->getState()->willReturn('tested-state-not-matching');
$this->isSatisfiedBy($event)->shouldReturn(false);
}
function it_satisfies_simple_to(TransitionEvent $event)
{
$specs = array('to' => 'tested-state');
$this->beConstructedWith($specs, $this->callable);
$event->getConfig()->willReturn($this->getConfig(array('dummy'), 'tested-state'));
$event->getTransition()->willReturn('dummy');
$event->getState()->willReturn('dummy');
$this->isSatisfiedBy($event)->shouldReturn(true);
}
function it_doesnt_satisfies_simple_to(TransitionEvent $event)
{
$specs = array('from' => 'tested-state');
$this->beConstructedWith($specs, $this->callable);
$event->getConfig()->willReturn($this->getConfig(array('tested-state-not-matching'), 'dummy'));
$event->getTransition()->willReturn('dummy');
$event->getState()->willReturn('dummy');
$this->isSatisfiedBy($event)->shouldReturn(false);
}
function it_satisfies_complex_specs(TransitionEvent $event)
{
$specs = array('to' => 'tested-state-to', 'from' => 'tested-state-from', 'on' => 'tested-transition');
$this->beConstructedWith($specs, $this->callable);
$event->getConfig()->willReturn($this->getConfig(array('tested-state-from'), 'tested-state-to'));
$event->getTransition()->willReturn('tested-transition');
$event->getState()->willReturn('tested-state-from');
$this->isSatisfiedBy($event)->shouldReturn(true);
}
function it_doesnt_satisfies_wrong_from(TransitionEvent $event)
{
$specs = array('to' => 'tested-state-to', 'from' => 'tested-wrong', 'on' => 'tested-transition');
$this->beConstructedWith($specs, $this->callable);
$event->getConfig()->willReturn($this->getConfig(array('dummy'), 'tested-state-to'));
$event->getTransition()->willReturn('tested-transition');
$event->getState()->willReturn('dummy');
$this->isSatisfiedBy($event)->shouldReturn(false);
}
function it_doesnt_satisfies_wrong_to(TransitionEvent $event)
{
$specs = array('to' => 'tested-wrong', 'from' => 'tested-state-from', 'on' => 'tested-transition');
$this->beConstructedWith($specs, $this->callable);
$event->getConfig()->willReturn($this->getConfig(array('tested-state-from'), 'dummy'));
$event->getTransition()->willReturn('tested-transition');
$event->getState()->willReturn('tested-state-from');
$this->isSatisfiedBy($event)->shouldReturn(false);
}
function it_doesnt_satisfies_wrong_on(TransitionEvent $event)
{
$specs = array('to' => 'tested-state-to', 'from' => 'tested-state-from', 'on' => 'tested-wrong');
$this->beConstructedWith($specs, $this->callable);
$event->getConfig()->willReturn($this->getConfig(array('tested-state-from'), 'tested-state-to'));
$event->getTransition()->willReturn('dummy');
$event->getState()->willReturn('tested-state-from');
$this->isSatisfiedBy($event)->shouldReturn(false);
}
function it_doesnt_satisfies_excluded_from(TransitionEvent $event)
{
$specs = array('to' => 'tested-state-to', 'excluded_from' => 'tested-state-from');
$this->beConstructedWith($specs, $this->callable);
$event->getConfig()->willReturn($this->getConfig(array('tested-state-from'), 'tested-state-to'));
$event->getTransition()->willReturn('dummy');
$event->getState()->willReturn('tested-state-from');
$this->isSatisfiedBy($event)->shouldReturn(false);
}
protected function getConfig($from = array(), $to)
{
return array('from' => $from, 'to' => $to);
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace spec\SM\Callback;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use SM\Event\TransitionEvent;
use SM\Factory\FactoryInterface;
use SM\StateMachine\StateMachineInterface;
use spec\SM\DummyObject;
class CascadeTransitionCallbackSpec extends ObjectBehavior
{
function let(FactoryInterface $factory)
{
$this->beConstructedWith($factory);
}
function it_is_initializable()
{
$this->shouldHaveType('SM\Callback\CascadeTransitionCallback');
}
function it_applies($factory, TransitionEvent $event, DummyObject $object, StateMachineInterface $sm)
{
$factory->get($object, 'graph')->willReturn($sm);
$sm->can('transition')->willReturn(true);
$sm->apply('transition', true)->shouldBeCalled();
$this->apply($object, $event, 'transition', 'graph');
}
function it_applies_with_default_graph(
$factory,
TransitionEvent $event,
DummyObject $object,
StateMachineInterface $sm1,
StateMachineInterface $sm2
) {
$event->getStateMachine()->willReturn($sm2);
$sm2->getGraph()->willReturn('graph');
$factory->get($object, 'graph')->willReturn($sm1);
$sm1->can('transition')->willReturn(true);
$sm1->apply('transition', true)->shouldBeCalled();
$this->apply($object, $event, 'transition');
}
function it_applies_with_default_graph_and_default_transition(
$factory,
TransitionEvent $event,
DummyObject $object,
StateMachineInterface $sm1,
StateMachineInterface $sm2
) {
$event->getStateMachine()->willReturn($sm2);
$event->getTransition()->willReturn('transition');
$sm2->getGraph()->willReturn('graph');
$factory->get($object, 'graph')->willReturn($sm1);
$sm1->can('transition')->willReturn(true);
$sm1->apply('transition', true)->shouldBeCalled();
$this->apply($object, $event);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace spec\SM;
class DummyObject
{
public function getState()
{
}
public function setState($state)
{
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace spec\SM\Extension\Twig;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use SM\Factory\FactoryInterface;
use SM\StateMachine\StateMachineInterface;
use spec\SM\DummyObject;
class SMExtensionSpec extends ObjectBehavior
{
function let(FactoryInterface $factory, StateMachineInterface $stateMachine)
{
$this->beConstructedWith($factory);
$factory->get(new DummyObject(), 'simple')->willReturn($stateMachine);
}
function it_is_initializable()
{
$this->shouldHaveType('SM\Extension\Twig\SMExtension');
}
function it_is_a_twig_extension()
{
$this->shouldBeAnInstanceOf('\Twig_Extension');
}
function it_should_have_a_name()
{
$this->getName()->shouldReturn('sm');
}
function it_provide_sm_can_function(FactoryInterface $factory, StateMachineInterface $stateMachine)
{
$this->can($object = new DummyObject(), 'new', 'simple');
$factory->get($object, 'simple')->shouldHaveBeenCalled();
$stateMachine->can('new')->shouldHaveBeenCalled();
}
function it_provide_sm_getState_function(FactoryInterface $factory, StateMachineInterface $stateMachine)
{
$this->getState($object = new DummyObject(), 'simple');
$factory->get($object, 'simple')->shouldHaveBeenCalled();
$stateMachine->getState()->shouldHaveBeenCalled();
}
function it_provide_sm_getPossibleTransitions_function(FactoryInterface $factory, StateMachineInterface $stateMachine)
{
$this->getPossibleTransitions($object = new DummyObject(), 'simple');
$factory->get($object, 'simple')->shouldHaveBeenCalled();
$stateMachine->getPossibleTransitions()->shouldHaveBeenCalled();
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace spec\SM\Factory;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use SM\Callback\CallbackFactoryInterface;
use spec\SM\DummyObject;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class FactorySpec extends ObjectBehavior
{
protected $configs = array(
'graph1' => array('state_machine_class' => 'SM\\StateMachine\\StateMachine', 'class' => 'spec\\SM\\DummyObject'),
'graph2' => array('class' => 'spec\\SM\\DummyObject'),
);
function let(EventDispatcherInterface $dispatcher, CallbackFactoryInterface $callbackFactory)
{
$this->beConstructedWith($this->configs, $dispatcher, $callbackFactory);
}
function it_is_initializable()
{
$this->shouldHaveType('SM\Factory\Factory');
}
function it_creates_statemachine(DummyObject $object)
{
$graph = 'graph1';
$this->get($object, $graph)->shouldReturnAnInstanceOf($this->configs[$graph]['state_machine_class']);
}
function it_creates_statemachine_with_default_class(DummyObject $object)
{
$this->get($object, 'graph2')->shouldReturnAnInstanceOf('SM\\StateMachine\\StateMachine');
}
function it_throws_exception_when_configuration_doesnt_exist(DummyObject $object)
{
$this->shouldThrow('SM\\SMException')->during('get', array($object, 'non-existing-graph'));
}
}

View File

@@ -0,0 +1,207 @@
<?php
namespace spec\SM\StateMachine;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use SM\Callback\CallbackFactoryInterface;
use SM\Callback\CallbackInterface;
use SM\Event\SMEvents;
use spec\SM\DummyObject;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class StateMachineSpec extends ObjectBehavior
{
protected $config = array(
'graph' => 'graph1',
'property_path' => 'state',
'states' => array('checkout', 'pending', 'confirmed', 'cancelled'),
'transitions' => array(
'create' => array(
'from' => array('checkout'),
'to' => 'pending'
),
'confirm' => array(
'from' => array('checkout', 'pending'),
'to' => 'confirmed'
),
'cancel' => array(
'from' => array('confirmed'),
'to' => 'cancelled'
)
),
'callbacks' => array(
'guard' => array(
'guard-confirm' => array(
'from' => array('pending'),
'do' => 'dummy'
)
),
'before' => array(
'from-checkout' => array(
'from' => array('checkout'),
'do' => 'dummy'
)
),
'after' => array(
'on-confirm' => array(
'on' => array('confirm'),
'do' => 'dummy'
),
'to-cancelled' => array(
'to' => array('cancelled'),
'do' => 'dummy'
)
)
)
);
function let(DummyObject $object, EventDispatcherInterface $dispatcher, CallbackFactoryInterface $callbackFactory)
{
$this->beConstructedWith($object, $this->config, $dispatcher, $callbackFactory);
}
function it_is_initializable()
{
$this->shouldHaveType('SM\StateMachine\StateMachine');
}
function it_can($object, $dispatcher, $callbackFactory, CallbackInterface $guard)
{
$object->getState()->shouldBeCalled()->willReturn('checkout');
$object->setState(Argument::any())->shouldNotBeCalled();
$dispatcher->dispatch(SMEvents::TEST_TRANSITION, Argument::type('SM\\Event\\TransitionEvent'))->shouldBeCalled();
$callbackFactory->get($this->config['callbacks']['guard']['guard-confirm'])->shouldBeCalled()->willReturn($guard);
$guard->__invoke(Argument::type('SM\\Event\\TransitionEvent'))->shouldBeCalled()->willReturn(true);
$this->can('create')->shouldReturn(true);
}
function it_cannot($object, $dispatcher)
{
$object->getState()->shouldBeCalled()->willReturn('cancel');
$object->setState(Argument::any())->shouldNotBeCalled();
$dispatcher->dispatch(Argument::any())->shouldNotBeCalled();
$this->can('create')->shouldReturn(false);
}
function it_is_guarded_and_can($object, $dispatcher, $callbackFactory, CallbackInterface $guard)
{
$object->getState()->shouldBeCalled()->willReturn('pending');
$object->setState(Argument::any())->shouldNotBeCalled();
$dispatcher->dispatch(SMEvents::TEST_TRANSITION, Argument::type('SM\\Event\\TransitionEvent'))->shouldBeCalled();
$callbackFactory->get($this->config['callbacks']['guard']['guard-confirm'])->shouldBeCalled()->willReturn($guard);
$guard->__invoke(Argument::type('SM\\Event\\TransitionEvent'))->shouldBeCalled()->willReturn(true);
$this->can('confirm')->shouldReturn(true);
}
function it_is_guarded_and_cannot($object, $dispatcher, $callbackFactory, CallbackInterface $guard)
{
$object->getState()->shouldBeCalled()->willReturn('pending');
$object->setState(Argument::any())->shouldNotBeCalled();
$dispatcher->dispatch(SMEvents::TEST_TRANSITION, Argument::type('SM\\Event\\TransitionEvent'))->shouldBeCalled();
$callbackFactory->get($this->config['callbacks']['guard']['guard-confirm'])->shouldBeCalled()->willReturn($guard);
$guard->__invoke(Argument::type('SM\\Event\\TransitionEvent'))->shouldBeCalled()->willReturn(false);
$this->can('confirm')->shouldReturn(false);
}
function it_throws_an_exception_if_transition_doesnt_exist_on_can()
{
$this->shouldThrow('SM\\SMException')->during('can', array('non-existing-transition'));
}
function it_applies_transition(
$object,
$dispatcher,
$callbackFactory,
CallbackInterface $guard,
CallbackInterface $callback1,
CallbackInterface $callback2,
CallbackInterface $callback3
) {
$object->getState()->shouldBeCalled()->willReturn('checkout');
$object->setState('confirmed')->shouldBeCalled();
$dispatcher->dispatch(SMEvents::TEST_TRANSITION, Argument::type('SM\\Event\\TransitionEvent'))->shouldBeCalled();
$dispatcher->dispatch(SMEvents::PRE_TRANSITION, Argument::type('SM\\Event\\TransitionEvent'))->shouldBeCalled();
$dispatcher->dispatch(SMEvents::POST_TRANSITION, Argument::type('SM\\Event\\TransitionEvent'))->shouldBeCalled();
$callbackFactory->get($this->config['callbacks']['guard']['guard-confirm'])->shouldBeCalled()->willReturn($guard);
$callbackFactory->get($this->config['callbacks']['before']['from-checkout'])->shouldBeCalled()->willReturn($callback1);
$callbackFactory->get($this->config['callbacks']['after']['on-confirm'])->shouldBeCalled()->willReturn($callback2);
$callbackFactory->get($this->config['callbacks']['after']['to-cancelled'])->shouldBeCalled()->willReturn($callback3);
$guard->__invoke(Argument::type('SM\\Event\\TransitionEvent'))->shouldBeCalled()->willReturn(true);
$callback1->__invoke(Argument::type('SM\\Event\\TransitionEvent'))->shouldBeCalled();
$callback2->__invoke(Argument::type('SM\\Event\\TransitionEvent'))->shouldBeCalled();
$callback3->__invoke(Argument::type('SM\\Event\\TransitionEvent'))->shouldBeCalled();
$this->apply('confirm');
}
function it_throws_an_exception_if_transition_cannot_be_applied($object, $dispatcher)
{
$object->getState()->shouldBeCalled()->willReturn('cancel');
$object->setState(Argument::any())->shouldNotBeCalled();
$dispatcher->dispatch(Argument::any())->shouldNotBeCalled();
$this->shouldThrow('SM\\SMException')->during('apply', array('confirm'));
}
function it_does_nothing_if_transition_cannot_be_applied_in_soft_mode($object, $dispatcher)
{
$object->getState()->shouldBeCalled()->willReturn('cancel');
$object->setState(Argument::any())->shouldNotBeCalled();
$dispatcher->dispatch(Argument::any())->shouldNotBeCalled();
$this->apply('confirm', true);
}
function it_throws_an_exception_if_transition_doesnt_exist_on_apply()
{
$this->shouldThrow('SM\\SMException')->during('apply', array('non-existing-transition'));
}
function it_returns_current_state($object)
{
$object->getState()->shouldBeCalled()->willReturn('my-state');
$this->getState()->shouldReturn('my-state');
}
function it_returns_current_graph()
{
$this->getGraph()->shouldReturn($this->config['graph']);
}
function it_returns_current_object($object)
{
$this->getObject()->shouldReturn($object);
}
function it_returns_possible_transitions($object, $callbackFactory, CallbackInterface $guard)
{
$object->getState()->shouldBeCalled()->willReturn('checkout');
$callbackFactory->get($this->config['callbacks']['guard']['guard-confirm'])->shouldBeCalled()->willReturn($guard);
$guard->__invoke(Argument::type('SM\\Event\\TransitionEvent'))->shouldBeCalled()->willReturn(true);
$this->getPossibleTransitions()->shouldReturn(array('create', 'confirm'));
}
}