diff --git a/CHANGELOG.md b/CHANGELOG.md index d579019..a89330b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ See [GitHub releases](https://github.com/mll-lab/laravel-utils/releases). ## Unreleased +### Added + +- Add `MLL\LaravelUtils\ModelStates\MermaidStateConfigValidator` + +### Removed + +- Remove `MLL\LaravelUtils\ModelStates\HasStateManager::generateMermaidGraphAsString` + ## v6.0.0 ### Changed diff --git a/src/ModelStates/HasStateManager.php b/src/ModelStates/HasStateManager.php index 22f0741..057b76e 100644 --- a/src/ModelStates/HasStateManager.php +++ b/src/ModelStates/HasStateManager.php @@ -3,9 +3,6 @@ namespace MLL\LaravelUtils\ModelStates; use Illuminate\Database\Eloquent\Relations\MorphOne; -use JBZoo\MermaidPHP\Graph; -use JBZoo\MermaidPHP\Link; -use JBZoo\MermaidPHP\Node; use MLL\LaravelUtils\ModelStates\Exceptions\UnknownStateException; trait HasStateManager @@ -63,44 +60,6 @@ public function stateManager(): MorphOne ); } - public function generateMermaidGraphAsString(): string - { - $config = $this->stateClassConfig(); - - $graph = (new Graph(['abc_order' => true])) - ->addStyle('linkStyle default interpolate basis'); - - $possibleStateInstances = array_map( - static fn (string $stateClass): State => new $stateClass(), - $config->possibleStates(), - ); - - foreach ($possibleStateInstances as $state) { - $graph->addNode(new Node($state::name())); - } - - foreach ($possibleStateInstances as $state) { - foreach ($config->possibleNextStates($state) as $possibleNextState) { - $sourceNode = $graph->getNode($state::name()); - $targetNode = $graph->getNode($possibleNextState::name()); - - if ($sourceNode instanceof Node && $targetNode instanceof Node) { - $transition = $config->transition($state::class, $possibleNextState::class); - assert($transition !== null); - - $reflectionClass = new \ReflectionClass($transition); - $transitionName = $reflectionClass->getName() === DefaultTransition::class - ? '' - : $reflectionClass->getShortName(); - - $graph->addLink(new Link($sourceNode, $targetNode, $transitionName)); - } - } - } - - return $graph->__toString(); - } - public function stateClassConfig(): StateConfig { return $this->stateClass()::config(); diff --git a/src/ModelStates/MermaidStateConfigValidator.php b/src/ModelStates/MermaidStateConfigValidator.php new file mode 100644 index 0000000..195f51d --- /dev/null +++ b/src/ModelStates/MermaidStateConfigValidator.php @@ -0,0 +1,66 @@ +__toString(), + message: "The Mermaid graph did not match. See the actual generated graph at {$actualGraph->getLiveEditorUrl()}." + ); + } + + public static function assertStateConfigEquals(string $expectedGraphString, StateConfig $actualStateConfig): void + { + self::assertGraphEquals( + expectedGraphString: $expectedGraphString, + actualGraph: self::graphFromStateConfig($actualStateConfig), + ); + } + + public static function graphFromStateConfig(StateConfig $stateConfig): Graph + { + $graph = (new Graph(['abc_order' => true])) + ->addStyle('linkStyle default interpolate basis'); + + $possibleStateInstances = array_map( + static fn (string $stateClass): State => new $stateClass(), + $stateConfig->possibleStates(), + ); + + foreach ($possibleStateInstances as $state) { + $graph->addNode(new Node($state::name())); + } + + foreach ($possibleStateInstances as $state) { + foreach ($stateConfig->possibleNextStates($state) as $possibleNextState) { + $sourceNode = $graph->getNode($state::name()); + $targetNode = $graph->getNode($possibleNextState::name()); + + if ($sourceNode instanceof Node && $targetNode instanceof Node) { + $transition = $stateConfig->transition($state::class, $possibleNextState::class); + assert($transition !== null); + + $reflectionClass = new \ReflectionClass($transition); + $transitionName = $reflectionClass->getName() === DefaultTransition::class + ? '' + : $reflectionClass->getShortName(); + + $graph->addLink(new Link($sourceNode, $targetNode, $transitionName)); + } + } + } + + return $graph; + } +} diff --git a/tests/ModelStates/StateTest.php b/tests/ModelStates/StateTest.php index 2238654..0686530 100644 --- a/tests/ModelStates/StateTest.php +++ b/tests/ModelStates/StateTest.php @@ -2,6 +2,7 @@ namespace MLL\LaravelUtils\Tests\ModelStates; +use App\ModelStates\ModelStates\ModelState; use App\ModelStates\ModelStates\StateA; use App\ModelStates\ModelStates\StateB; use App\ModelStates\ModelStates\StateC; @@ -18,6 +19,7 @@ use MLL\LaravelUtils\ModelStates\Exceptions\TransitionNotAllowed; use MLL\LaravelUtils\ModelStates\Exceptions\TransitionNotFound; use MLL\LaravelUtils\ModelStates\Exceptions\UnknownStateException; +use MLL\LaravelUtils\ModelStates\MermaidStateConfigValidator; use MLL\LaravelUtils\ModelStates\TransitionDirection; use MLL\LaravelUtils\Tests\DBTestCase; @@ -182,9 +184,8 @@ public function testUnknownStateThrowsExceptionsForRetrieval(): void public function testGenerateMermaidGraph(): void { - $model = new TestModel(); - - $workflowAsMermaidGraph = 'graph TB; + $workflowAsMermaidGraph = /** @lang Mermaid */ <<<'MERMAID' +graph TB; FOO_BAR("FOO_BAR"); STATE_A("STATE_A"); STATE_C("STATE_C"); @@ -195,12 +196,9 @@ public function testGenerateMermaidGraph(): void STATE_A-->STATE_C; STATE_A-->STATE_D; -linkStyle default interpolate basis;'; +linkStyle default interpolate basis; +MERMAID; - self::assertSame( - $workflowAsMermaidGraph, - $model->generateMermaidGraphAsString(), - 'Use https://mermaid.live/ for debugging.' - ); + MermaidStateConfigValidator::assertStateConfigEquals($workflowAsMermaidGraph, ModelState::config()); } }