diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml
index b231c66..f363898 100644
--- a/.github/workflows/php.yml
+++ b/.github/workflows/php.yml
@@ -43,5 +43,8 @@ jobs:
- name: PHPStan
run: bin/phpstan
+ - name: Composer validate
+ run: composer validate
+
# - name: Infection
# run: bin/infection --formatter=progress
diff --git a/composer.json b/composer.json
index 17cc4a4..ea8813b 100644
--- a/composer.json
+++ b/composer.json
@@ -31,7 +31,7 @@
"infection/infection": "~0.13",
"phpstan/phpstan": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
- "phpunit/phpunit": "^8.5",
+ "phpunit/phpunit": "^10.0|^11.0|^12.0",
"squizlabs/php_codesniffer": "^3.2",
"symfony/cache": "^4.0|^5.0|^6.0"
},
diff --git a/examples/ContextUsingCustomMetadataTest.php b/examples/ContextUsingCustomMetadataTest.php
index 6b6e2d8..cd15ff1 100644
--- a/examples/ContextUsingCustomMetadataTest.php
+++ b/examples/ContextUsingCustomMetadataTest.php
@@ -64,7 +64,7 @@ public function test_it_should_allow_to_transit_from_pending_to_archived(): void
$this->assertTrue($context->state->isInState('archived'));
}
- public function test_it_should_allow_to_transit_from_approved_to_published(): ContextStub
+ public function test_it_should_allow_to_transit_from_approved_to_published(): void
{
$context = new ContextStub();
$context->approve();
@@ -73,8 +73,6 @@ public function test_it_should_allow_to_transit_from_approved_to_published(): Co
$context->publish();
$this->assertTrue($context->state->isInState('published'));
-
- return $context;
}
public function test_it_should_allow_to_transit_from_approved_to_archived(): void
@@ -100,7 +98,7 @@ public function test_it_should_allow_to_transit_from_published_to_approved(): vo
$this->assertTrue($context->state->isInState('approved'));
}
- public function test_it_should_allow_to_transit_from_published_to_archived(): ContextStub
+ public function test_it_should_allow_to_transit_from_published_to_archived(): void
{
$context = new ContextStub();
$context->approve();
@@ -110,8 +108,6 @@ public function test_it_should_allow_to_transit_from_published_to_archived(): Co
$context->archive();
$this->assertTrue($context->state->isInState('archived'));
-
- return $context;
}
public function test_it_should_allow_to_transit_from_archived_to_pending(): void
@@ -125,7 +121,7 @@ public function test_it_should_allow_to_transit_from_archived_to_pending(): void
$this->assertTrue($context->state->isInState('pending'));
}
- public function test_it_should_allow_to_transit_from_archived_to_approved(): ContextStub
+ public function test_it_should_allow_to_transit_from_archived_to_approved(): void
{
$context = new ContextStub();
$context->discard();
@@ -134,8 +130,6 @@ public function test_it_should_allow_to_transit_from_archived_to_approved(): Con
$context->unArchive();
$this->assertTrue($context->state->isInState('approved'));
-
- return $context;
}
public function test_attributes_of_pending(): void
@@ -146,34 +140,35 @@ public function test_attributes_of_pending(): void
$this->assertFalse($context->isVisible());
}
- /**
- * @param ContextStub $context
- * @depends test_it_should_allow_to_transit_from_archived_to_approved
- */
- public function test_attributes_of_approved(ContextStub $context): void
+ public function test_attributes_of_approved(): void
{
+ $context = new ContextStub();
+ $context->discard();
+ $context->unArchive();
+
$this->assertTrue($context->state->isInState('approved'));
$this->assertTrue($context->isDraft());
$this->assertFalse($context->isVisible());
}
- /**
- * @param ContextStub $context
- * @depends test_it_should_allow_to_transit_from_approved_to_published
- */
- public function test_attributes_of_published(ContextStub $context): void
+ public function test_attributes_of_published(): void
{
+ $context = new ContextStub();
+ $context->approve();
+ $context->publish();
+
$this->assertTrue($context->state->isInState('published'));
$this->assertFalse($context->isDraft());
$this->assertTrue($context->isVisible());
}
- /**
- * @param ContextStub $context
- * @depends test_it_should_allow_to_transit_from_published_to_archived
- */
- public function test_attributes_of_archived(ContextStub $context): void
+ public function test_attributes_of_archived(): void
{
+ $context = new ContextStub();
+ $context->approve();
+ $context->publish();
+ $context->archive();
+
$this->assertTrue($context->state->isInState('archived'));
$this->assertFalse($context->isDraft());
$this->assertFalse($context->isVisible());
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index d57baea..0b8da95 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -2,18 +2,13 @@
+ backupGlobals="false"
+ backupStaticProperties="false"
+ colors="true"
+ beStrictAboutOutputDuringTests="true"
+ failOnAllIssues="true"
+ bootstrap="vendor/autoload.php"
+>
@@ -25,9 +20,9 @@
-
-
- src
-
-
+
+
+ src
+
+
diff --git a/src/StateMachine.php b/src/StateMachine.php
index 448e388..16748c3 100644
--- a/src/StateMachine.php
+++ b/src/StateMachine.php
@@ -7,6 +7,7 @@
namespace Star\Component\State;
+use Closure;
use Star\Component\State\Callbacks\AlwaysThrowExceptionOnFailure;
use Star\Component\State\Callbacks\TransitionCallback;
use Star\Component\State\Event\StateEventStore;
@@ -39,8 +40,11 @@ public function __construct(
* @throws InvalidStateTransitionException
* @throws NotFoundException
*/
- public function transit(string $transitionName, $context, TransitionCallback $callback = null): string
- {
+ public function transit(
+ string $transitionName,
+ mixed $context,
+ ?TransitionCallback $callback = null
+ ): string {
if (!$callback) {
$callback = new AlwaysThrowExceptionOnFailure();
}
@@ -101,7 +105,7 @@ public function hasAttribute(string $attribute): bool
return $this->states->hasAttribute($this->currentState, $attribute);
}
- public function addListener(string $event, \Closure $listener): void
+ public function addListener(string $event, Closure $listener): void
{
$this->listeners->addListener($event, $listener);
}
diff --git a/src/StateMetadata.php b/src/StateMetadata.php
index edc446a..b3310c5 100644
--- a/src/StateMetadata.php
+++ b/src/StateMetadata.php
@@ -39,8 +39,11 @@ private function getMachine(): StateMachine
*
* @return static
*/
- final public function transit(string $name, $context, TransitionCallback $callback = null): StateMetadata
- {
+ final public function transit(
+ string $name,
+ $context,
+ ?TransitionCallback $callback = null
+ ): StateMetadata {
$this->current = $this->getMachine()->transit($name, $context, $callback);
return $this;
diff --git a/tests/StateMachineTest.php b/tests/StateMachineTest.php
index bcecc9a..9b4b021 100644
--- a/tests/StateMachineTest.php
+++ b/tests/StateMachineTest.php
@@ -7,30 +7,29 @@
namespace Star\Component\State;
-use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Star\Component\State\Event\StateEventStore;
use Star\Component\State\Event\TransitionWasFailed;
use Star\Component\State\Event\TransitionWasSuccessful;
use Star\Component\State\Event\TransitionWasRequested;
+use Star\Component\State\Stub\EventRegistrySpy;
use Star\Component\State\Transitions\OneToOneTransition;
+use stdClass;
+use Throwable;
final class StateMachineTest extends TestCase
{
private TransitionRegistry $registry;
private StateMachine $machine;
private TestContext $context;
- /**
- * @var MockObject|EventRegistry
- */
- private $listeners;
+ private EventRegistrySpy $events;
public function setUp(): void
{
- $this->listeners = $this->createMock(EventRegistry::class);
+ $this->events = new EventRegistrySpy();
$this->context = new TestContext();
$this->registry = new TransitionRegistry();
- $this->machine = new StateMachine('current', $this->registry, $this->listeners);
+ $this->machine = new StateMachine('current', $this->registry, $this->events);
}
public function test_it_should_not_allow_to_transition_to_a_not_configured_transition(): void
@@ -52,30 +51,22 @@ public function test_it_should_transition_from_one_state_to_the_other(): void
public function test_it_should_trigger_an_event_before_any_transition(): void
{
- $this->listeners
- ->expects($this->at(0))
- ->method('dispatch')
- ->with(
- StateEventStore::BEFORE_TRANSITION,
- $this->isInstanceOf(TransitionWasRequested::class)
- );
-
$this->registry->addTransition(new OneToOneTransition('name', 'current', 'next'));
$this->machine->transit('name', $this->context);
+ $name = StateEventStore::BEFORE_TRANSITION;
+ $events = $this->events->getDispatchedEvents($name);
+ self::assertCount(1, $events);
+ self::assertContainsOnlyInstancesOf(TransitionWasRequested::class, $events);
}
public function test_it_should_trigger_an_event_after_any_transition(): void
{
- $this->listeners
- ->expects($this->at(1))
- ->method('dispatch')
- ->with(
- StateEventStore::AFTER_TRANSITION,
- $this->isInstanceOf(TransitionWasSuccessful::class)
- );
-
$this->registry->addTransition(new OneToOneTransition('name', 'current', 'next'));
$this->machine->transit('name', $this->context);
+ $name = StateEventStore::AFTER_TRANSITION;
+ $events = $this->events->getDispatchedEvents($name);
+ self::assertCount(1, $events);
+ self::assertContainsOnlyInstancesOf(TransitionWasSuccessful::class, $events);
}
public function test_it_should_throw_exception_with_class_context_when_transition_not_allowed(): void
@@ -87,7 +78,7 @@ public function test_it_should_throw_exception_with_class_context_when_transitio
$this->expectExceptionMessage(
"The transition 't' is not allowed when context 'stdClass' is in state 'current'."
);
- $this->machine->transit('t', new \stdClass);
+ $this->machine->transit('t', new stdClass);
}
public function test_it_should_throw_exception_with_context_as_string_when_transition_not_allowed(): void
@@ -112,7 +103,7 @@ public function test_state_can_have_attribute(): void
public function test_it_should_visit_the_transitions(): void
{
$registry = $this->createMock(StateRegistry::class);
- $machine = new StateMachine('', $registry, $this->listeners);
+ $machine = new StateMachine('', $registry, $this->events);
$visitor = $this->createMock(TransitionVisitor::class);
$registry
@@ -131,20 +122,18 @@ public function test_it_should_throw_exception_when_state_do_not_exists(): void
public function test_it_should_dispatch_an_event_before_a_transition_has_failed(): void
{
- $this->listeners
- ->expects($this->at(1))
- ->method('dispatch')
- ->with(
- StateEventStore::FAILURE_TRANSITION,
- $this->isInstanceOf(TransitionWasFailed::class)
- );
-
$this->registry->addTransition(new OneToOneTransition('t', 'from', 'to'));
try {
$this->machine->transit('t', 'context');
$this->fail('An exception should have been thrown');
- } catch (InvalidStateTransitionException $exception) {
+ } catch (Throwable $exception) {
// silence it
+ self::assertInstanceOf(InvalidStateTransitionException::class, $exception);
}
+
+ $name = StateEventStore::FAILURE_TRANSITION;
+ $events = $this->events->getDispatchedEvents($name);
+ self::assertCount(1, $events);
+ self::assertContainsOnlyInstancesOf(TransitionWasFailed::class, $events);
}
}
diff --git a/tests/Stub/EventRegistrySpy.php b/tests/Stub/EventRegistrySpy.php
new file mode 100644
index 0000000..dae3c59
--- /dev/null
+++ b/tests/Stub/EventRegistrySpy.php
@@ -0,0 +1,45 @@
+
+ */
+ private array $listeners = [];
+
+ /**
+ * @var array>
+ */
+ private array $dispatches = [];
+
+ public function dispatch(string $name, StateEvent $event): void
+ {
+ $this->dispatches[$name][] = $event;
+ }
+
+ public function addListener(string $event, callable $listener): void
+ {
+ $this->listeners[$event][] = $listener;
+ }
+
+ /**
+ * @return StateEvent[]
+ */
+ public function getDispatchedEvents(string $event): array
+ {
+ return $this->dispatches[$event] ?? [];
+ }
+
+ /**
+ * @return callable[]
+ */
+ public function getListenersOfEvent(string $event): array
+ {
+ return $this->listeners[$event] ?? [];
+ }
+}
diff --git a/tests/Stub/RegistrySpy.php b/tests/Stub/RegistryBuilderSpy.php
similarity index 95%
rename from tests/Stub/RegistrySpy.php
rename to tests/Stub/RegistryBuilderSpy.php
index a61b39e..c34d806 100644
--- a/tests/Stub/RegistrySpy.php
+++ b/tests/Stub/RegistryBuilderSpy.php
@@ -4,7 +4,7 @@
use Star\Component\State\RegistryBuilder;
-final class RegistrySpy implements RegistryBuilder
+final class RegistryBuilderSpy implements RegistryBuilder
{
/**
* @var arraygetMockBuilder(StateVisitor::class)->getMock();
- $visitor
- ->expects($this->at(0))
- ->method('visitState')
- ->with('from', ['attr']);
- $visitor
- ->expects($this->at(1))
- ->method('visitState')
- ->with('to', []);
+ $visitor = new class implements StateVisitor
+ {
+ /**
+ * @var array>
+ */
+ public array $attributes = [];
+
+ public function visitState(string $name, array $attributes): void
+ {
+ $this->attributes[$name][] = $attributes;
+ }
+ };
$this->registry->addTransition(new OneToOneTransition('t', 'from', 'to'));
$this->registry->addAttribute('from', 'attr');
$this->registry->acceptStateVisitor($visitor);
+ self::assertSame(
+ [
+ 'from' => [
+ 0 => [
+ 0 => 'attr',
+ ],
+ ],
+ 'to' => [
+ 0 => [],
+ ],
+ ],
+ $visitor->attributes
+ );
}
}
diff --git a/tests/Transitions/ManyToOneTransitionTest.php b/tests/Transitions/ManyToOneTransitionTest.php
index 6dc7424..486ba79 100644
--- a/tests/Transitions/ManyToOneTransitionTest.php
+++ b/tests/Transitions/ManyToOneTransitionTest.php
@@ -3,7 +3,7 @@
namespace Star\Component\State\Transitions;
use PHPUnit\Framework\TestCase;
-use Star\Component\State\Stub\RegistrySpy;
+use Star\Component\State\Stub\RegistryBuilderSpy;
final class ManyToOneTransitionTest extends TestCase
{
@@ -21,7 +21,7 @@ public function test_it_should_have_a_name(): void
public function test_it_should_register_the_from_and_to_states(): void
{
- $registry = new RegistrySpy();
+ $registry = new RegistryBuilderSpy();
$this->transition->onRegister($registry);
diff --git a/tests/Transitions/OneToOneTransitionTest.php b/tests/Transitions/OneToOneTransitionTest.php
index 9111dbb..0226613 100644
--- a/tests/Transitions/OneToOneTransitionTest.php
+++ b/tests/Transitions/OneToOneTransitionTest.php
@@ -3,7 +3,7 @@
namespace Star\Component\State\Transitions;
use PHPUnit\Framework\TestCase;
-use Star\Component\State\Stub\RegistrySpy;
+use Star\Component\State\Stub\RegistryBuilderSpy;
final class OneToOneTransitionTest extends TestCase
{
@@ -21,7 +21,7 @@ public function test_it_should_have_a_name(): void
public function test_it_should_register_the_from_and_to_states(): void
{
- $registry = new RegistrySpy();
+ $registry = new RegistryBuilderSpy();
$this->transition->onRegister($registry);