From 5889c35bba3f8e104ed72e122bcdfd366bad5896 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 9 Apr 2022 00:35:43 +0700 Subject: [PATCH 01/85] Wait for video container --- composer.json | 2 +- src/Entity/Bug.php | 3 +- src/Entity/Task.php | 3 +- src/Reducer/HandlerTemplate.php | 71 +--- src/Repository/BugRepository.php | 54 +++ src/Repository/BugRepositoryInterface.php | 15 + src/Repository/TaskRepository.php | 30 ++ src/Repository/TaskRepositoryInterface.php | 13 + src/Resources/config/services.php | 74 ++-- src/Service/Bug/BugHelper.php | 51 +-- src/Service/Bug/BugProgress.php | 43 --- src/Service/Bug/BugProgressInterface.php | 12 - src/Service/StepsRunner.php | 72 ++++ src/Service/StepsRunnerInterface.php | 17 + src/Service/Task/TaskHelper.php | 102 ++---- .../RunTaskMessageHandlerTest.php | 4 +- tests/Reducer/HandlerTestCase.php | 163 +++------ tests/Reducer/Random/RandomHandlerTest.php | 8 +- tests/Reducer/Split/SplitHandlerTest.php | 8 +- tests/Repository/BugRepositoryTest.php | 153 ++++++++ tests/Repository/TaskRepositoryTest.php | 63 ++++ tests/Service/Bug/BugHelperTest.php | 244 +++---------- tests/Service/Bug/BugProgressTest.php | 88 ----- tests/Service/SelenoidHelperTest.php | 4 +- tests/Service/StepsRunnerTest.php | 326 ++++++++++++++++++ tests/Service/Task/TaskHelperTest.php | 207 +++++------ tests/StepsTestCase.php | 24 -- 27 files changed, 1041 insertions(+), 813 deletions(-) create mode 100644 src/Repository/BugRepository.php create mode 100644 src/Repository/BugRepositoryInterface.php create mode 100644 src/Repository/TaskRepository.php create mode 100644 src/Repository/TaskRepositoryInterface.php delete mode 100644 src/Service/Bug/BugProgress.php delete mode 100644 src/Service/Bug/BugProgressInterface.php create mode 100644 src/Service/StepsRunner.php create mode 100644 src/Service/StepsRunnerInterface.php create mode 100644 tests/Repository/BugRepositoryTest.php create mode 100644 tests/Repository/TaskRepositoryTest.php delete mode 100644 tests/Service/Bug/BugProgressTest.php create mode 100644 tests/Service/StepsRunnerTest.php delete mode 100644 tests/StepsTestCase.php diff --git a/composer.json b/composer.json index cb39873f..f52c57d7 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require": { "php": "^8.0", "ext-json": "*", - "doctrine/annotations": "^1.13", + "doctrine/doctrine-bundle": "^2.6", "doctrine/orm": "^2.8", "florianv/petrinet": "^2.1", "jmgq/a-star": "^2.1", diff --git a/src/Entity/Bug.php b/src/Entity/Bug.php index 10505d75..121222fd 100644 --- a/src/Entity/Bug.php +++ b/src/Entity/Bug.php @@ -9,9 +9,10 @@ use Tienvx\Bundle\MbtBundle\Model\Bug as BugModel; use Tienvx\Bundle\MbtBundle\Model\ProgressInterface; use Tienvx\Bundle\MbtBundle\Model\TaskInterface; +use Tienvx\Bundle\MbtBundle\Repository\BugRepository; /** - * @ORM\Entity + * @ORM\Entity(repositoryClass=BugRepository::class) * @ORM\HasLifecycleCallbacks */ class Bug extends BugModel diff --git a/src/Entity/Task.php b/src/Entity/Task.php index 8dd0d120..bf7b2e76 100644 --- a/src/Entity/Task.php +++ b/src/Entity/Task.php @@ -12,9 +12,10 @@ use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; use Tienvx\Bundle\MbtBundle\Model\Task as TaskModel; use Tienvx\Bundle\MbtBundle\Model\Task\BrowserInterface; +use Tienvx\Bundle\MbtBundle\Repository\TaskRepository; /** - * @ORM\Entity + * @ORM\Entity(repositoryClass=TaskRepository::class) * @ORM\HasLifecycleCallbacks */ class Task extends TaskModel diff --git a/src/Reducer/HandlerTemplate.php b/src/Reducer/HandlerTemplate.php index ca799e1d..264ab79e 100644 --- a/src/Reducer/HandlerTemplate.php +++ b/src/Reducer/HandlerTemplate.php @@ -2,46 +2,33 @@ namespace Tienvx\Bundle\MbtBundle\Reducer; -use Doctrine\DBAL\LockMode; -use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Messenger\MessageBusInterface; use Throwable; -use Tienvx\Bundle\MbtBundle\Exception\ExceptionInterface; use Tienvx\Bundle\MbtBundle\Message\ReduceBugMessage; use Tienvx\Bundle\MbtBundle\Model\BugInterface; -use Tienvx\Bundle\MbtBundle\Service\Bug\BugHelperInterface; -use Tienvx\Bundle\MbtBundle\Service\SelenoidHelperInterface; -use Tienvx\Bundle\MbtBundle\Service\StepRunnerInterface; +use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\StepsBuilderInterface; +use Tienvx\Bundle\MbtBundle\Service\StepsRunnerInterface; abstract class HandlerTemplate implements HandlerInterface { - protected EntityManagerInterface $entityManager; + protected BugRepositoryInterface $bugRepository; protected MessageBusInterface $messageBus; - protected StepRunnerInterface $stepRunner; + protected StepsRunnerInterface $stepsRunner; protected StepsBuilderInterface $stepsBuilder; - protected BugHelperInterface $bugHelper; - protected SelenoidHelperInterface $selenoidHelper; public function __construct( - EntityManagerInterface $entityManager, + BugRepositoryInterface $bugRepository, MessageBusInterface $messageBus, - StepRunnerInterface $stepRunner, - StepsBuilderInterface $stepsBuilder, - BugHelperInterface $bugHelper, - SelenoidHelperInterface $selenoidHelper + StepsRunnerInterface $stepsRunner, + StepsBuilderInterface $stepsBuilder ) { - $this->entityManager = $entityManager; + $this->bugRepository = $bugRepository; $this->messageBus = $messageBus; - $this->stepRunner = $stepRunner; + $this->stepsRunner = $stepsRunner; $this->stepsBuilder = $stepsBuilder; - $this->bugHelper = $bugHelper; - $this->selenoidHelper = $selenoidHelper; } - /** - * @throws ExceptionInterface - */ public function handle(BugInterface $bug, int $from, int $to): void { $newSteps = iterator_to_array($this->stepsBuilder->create($bug, $from, $to)); @@ -49,45 +36,11 @@ public function handle(BugInterface $bug, int $from, int $to): void return; } - $this->run($newSteps, $bug); - } - - /** - * @throws ExceptionInterface - */ - protected function run(array $newSteps, BugInterface $bug): void - { - $driver = $this->selenoidHelper->createDriver($this->selenoidHelper->getCapabilities($bug)); - try { - foreach ($newSteps as $step) { - $this->stepRunner->run($step, $bug->getTask()->getModelRevision(), $driver); - } - } catch (ExceptionInterface $exception) { - throw $exception; - } catch (Throwable $throwable) { + $this->stepsRunner->run($newSteps, $bug, false, function (Throwable $throwable) use ($bug, $newSteps): void { if ($throwable->getMessage() === $bug->getMessage()) { - $this->updateSteps($bug, $newSteps); + $this->bugRepository->updateSteps($bug, $newSteps); $this->messageBus->dispatch(new ReduceBugMessage($bug->getId())); } - } finally { - $driver->quit(); - } - } - - public function updateSteps(BugInterface $bug, array $newSteps): void - { - $callback = function () use ($bug, $newSteps): void { - // Refresh the bug for the latest steps's length. - $this->entityManager->refresh($bug); - - if (count($newSteps) <= count($bug->getSteps())) { - $this->entityManager->lock($bug, LockMode::PESSIMISTIC_WRITE); - $bug->getProgress()->setTotal(0); - $bug->getProgress()->setProcessed(0); - $bug->setSteps($newSteps); - } - }; - - $this->entityManager->transactional($callback); + }); } } diff --git a/src/Repository/BugRepository.php b/src/Repository/BugRepository.php new file mode 100644 index 00000000..7b08e485 --- /dev/null +++ b/src/Repository/BugRepository.php @@ -0,0 +1,54 @@ +getEntityManager()->wrapInTransaction(function () use ($bug, $newSteps): void { + // Refresh the bug for the latest steps's length. + $this->getEntityManager()->refresh($bug); + + if (count($newSteps) <= count($bug->getSteps())) { + $this->getEntityManager()->lock($bug, LockMode::PESSIMISTIC_WRITE); + $bug->getProgress()->setTotal(0); + $bug->getProgress()->setProcessed(0); + $bug->setSteps($newSteps); + } + }); + } + + public function increaseProcessed(BugInterface $bug, int $processed = 1): void + { + $this->getEntityManager()->wrapInTransaction(function () use ($bug, $processed): void { + // Refresh the bug for the latest progress. + $this->getEntityManager()->refresh($bug); + + $this->getEntityManager()->lock($bug, LockMode::PESSIMISTIC_WRITE); + $bug->getProgress()->increase($processed); + }); + } + + public function increaseTotal(BugInterface $bug, int $total): void + { + $this->getEntityManager()->wrapInTransaction(function () use ($bug, $total): void { + // Refresh the bug for the latest progress. + $this->getEntityManager()->refresh($bug); + + $this->getEntityManager()->lock($bug, LockMode::PESSIMISTIC_WRITE); + $bug->getProgress()->setTotal($bug->getProgress()->getTotal() + $total); + }); + } +} diff --git a/src/Repository/BugRepositoryInterface.php b/src/Repository/BugRepositoryInterface.php new file mode 100644 index 00000000..39a8c393 --- /dev/null +++ b/src/Repository/BugRepositoryInterface.php @@ -0,0 +1,15 @@ +setRunning(true); + $this->getEntityManager()->flush(); + } + + public function stopRunning(TaskInterface $task): void + { + $task->setRunning(false); + // Running task take long time. Reconnect to flush changes. + $this->getEntityManager()->getConnection()->connect(); + $this->getEntityManager()->flush(); + } +} diff --git a/src/Repository/TaskRepositoryInterface.php b/src/Repository/TaskRepositoryInterface.php new file mode 100644 index 00000000..5e33da05 --- /dev/null +++ b/src/Repository/TaskRepositoryInterface.php @@ -0,0 +1,13 @@ +autoconfigure(true) + // Message handlers ->set(RunTaskMessageHandler::class) ->args([ service(TaskHelperInterface::class), ]) ->autoconfigure(true) - ->set(RecordVideoMessageHandler::class) ->args([ service(BugHelperInterface::class), ]) ->autoconfigure(true) - ->set(ReduceBugMessageHandler::class) ->args([ service(BugHelperInterface::class), ]) ->autoconfigure(true) - ->set(ReduceStepsMessageHandler::class) ->args([ service(BugHelperInterface::class), ]) ->autoconfigure(true) - ->set(ReportBugMessageHandler::class) ->args([ service(BugHelperInterface::class), ]) ->autoconfigure(true) - ->set(ReducerManager::class) ->alias(ReducerManagerInterface::class, ReducerManager::class) + // Reducers ->set(RandomDispatcher::class) ->args([ service(MessageBusInterface::class), ]) ->set(RandomHandler::class) ->args([ - service(EntityManagerInterface::class), + service(BugRepositoryInterface::class), service(MessageBusInterface::class), - service(StepRunnerInterface::class), + service(StepsRunnerInterface::class), service(StepsBuilderInterface::class), - service(BugHelperInterface::class), - service(SelenoidHelperInterface::class), ]) ->set(RandomReducer::class) ->args([ @@ -150,12 +149,10 @@ ]) ->set(SplitHandler::class) ->args([ - service(EntityManagerInterface::class), + service(BugRepositoryInterface::class), service(MessageBusInterface::class), - service(StepRunnerInterface::class), + service(StepsRunnerInterface::class), service(StepsBuilderInterface::class), - service(BugHelperInterface::class), - service(SelenoidHelperInterface::class), ]) ->set(SplitReducer::class) ->args([ @@ -164,6 +161,7 @@ ]) ->autoconfigure(true) + // Validators ->set(TagsValidator::class) ->set(ValidCommandValidator::class) ->args([ @@ -173,9 +171,9 @@ 'alias' => ValidCommandValidator::class, ]) + // Commands ->set(CommandPreprocessor::class) ->alias(CommandPreprocessorInterface::class, CommandPreprocessor::class) - ->set(CommandRunnerManager::class) ->args([ tagged_iterator(CommandRunnerInterface::TAG), @@ -200,6 +198,20 @@ ->set(WindowCommandRunner::class) ->autoconfigure(true) + // Repositories + ->set(BugRepository::class) + ->args([ + service(ManagerRegistry::class), + ]) + ->tag('doctrine.repository_service') + ->alias(BugRepositoryInterface::class, BugRepository::class) + ->set(TaskRepository::class) + ->args([ + service(ManagerRegistry::class), + ]) + ->tag('doctrine.repository_service') + ->alias(TaskRepositoryInterface::class, TaskRepository::class) + // Services ->set(ExpressionLanguage::class) @@ -212,32 +224,23 @@ ->set(ModelHelper::class) ->alias(ModelHelperInterface::class, ModelHelper::class) - ->set(BugProgress::class) - ->args([ - service(EntityManagerInterface::class), - ]) - ->alias(BugProgressInterface::class, BugProgress::class) - ->set(BugHelper::class) ->args([ - service(ReducerManager::class), - service(EntityManagerInterface::class), + service(ReducerManagerInterface::class), + service(BugRepositoryInterface::class), service(MessageBusInterface::class), - service(BugProgressInterface::class), service(BugNotifierInterface::class), - service(StepRunnerInterface::class), - service(SelenoidHelperInterface::class), + service(StepsRunnerInterface::class), service(ConfigInterface::class), ]) ->alias(BugHelperInterface::class, BugHelper::class) ->set(TaskHelper::class) ->args([ - service(GeneratorManager::class), - service(EntityManagerInterface::class), - service(StepRunnerInterface::class), + service(GeneratorManagerInterface::class), + service(TaskRepositoryInterface::class), + service(StepsRunnerInterface::class), service(BugHelperInterface::class), - service(SelenoidHelperInterface::class), service(ConfigInterface::class), ]) ->alias(TaskHelperInterface::class, TaskHelper::class) @@ -262,6 +265,13 @@ ]) ->alias(StepRunnerInterface::class, StepRunner::class) + ->set(StepsRunner::class) + ->args([ + service(SelenoidHelperInterface::class), + service(StepRunnerInterface::class), + ]) + ->alias(StepsRunnerInterface::class, StepsRunner::class) + ->set(MarkingHelper::class) ->args([ service(ColorfulFactoryInterface::class), diff --git a/src/Service/Bug/BugHelper.php b/src/Service/Bug/BugHelper.php index 1d024a04..4ceadc99 100644 --- a/src/Service/Bug/BugHelper.php +++ b/src/Service/Bug/BugHelper.php @@ -2,9 +2,7 @@ namespace Tienvx\Bundle\MbtBundle\Service\Bug; -use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Messenger\MessageBusInterface; -use Throwable; use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Exception\ExceptionInterface; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; @@ -12,38 +10,32 @@ use Tienvx\Bundle\MbtBundle\Message\ReportBugMessage; use Tienvx\Bundle\MbtBundle\Model\BugInterface; use Tienvx\Bundle\MbtBundle\Reducer\ReducerManagerInterface; +use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; -use Tienvx\Bundle\MbtBundle\Service\SelenoidHelperInterface; -use Tienvx\Bundle\MbtBundle\Service\StepRunnerInterface; +use Tienvx\Bundle\MbtBundle\Service\StepsRunnerInterface; class BugHelper implements BugHelperInterface { protected ReducerManagerInterface $reducerManager; - protected EntityManagerInterface $entityManager; + protected BugRepositoryInterface $bugRepository; protected MessageBusInterface $messageBus; - protected BugProgressInterface $bugProgress; - protected BugNotifierInterface $notifyHelper; - protected StepRunnerInterface $stepRunner; - protected SelenoidHelperInterface $selenoidHelper; + protected BugNotifierInterface $bugNotifier; + protected StepsRunnerInterface $stepsRunner; protected ConfigInterface $config; public function __construct( ReducerManagerInterface $reducerManager, - EntityManagerInterface $entityManager, + BugRepositoryInterface $bugRepository, MessageBusInterface $messageBus, - BugProgressInterface $bugProgress, - BugNotifierInterface $notifyHelper, - StepRunnerInterface $stepRunner, - SelenoidHelperInterface $selenoidHelper, + BugNotifierInterface $bugNotifier, + StepsRunnerInterface $stepsRunner, ConfigInterface $config ) { $this->reducerManager = $reducerManager; - $this->entityManager = $entityManager; + $this->bugRepository = $bugRepository; $this->messageBus = $messageBus; - $this->bugProgress = $bugProgress; - $this->notifyHelper = $notifyHelper; - $this->stepRunner = $stepRunner; - $this->selenoidHelper = $selenoidHelper; + $this->bugNotifier = $bugNotifier; + $this->stepsRunner = $stepsRunner; $this->config = $config; } @@ -54,7 +46,7 @@ public function reduceBug(int $bugId): void $reducer = $this->reducerManager->getReducer($this->config->getReducer()); $messagesCount = $reducer->dispatch($bug); if ($messagesCount > 0) { - $this->bugProgress->increaseTotal($bug, $messagesCount); + $this->bugRepository->increaseTotal($bug, $messagesCount); } elseif ($bug->getProgress()->getProcessed() === $bug->getProgress()->getTotal()) { $this->recordAndReport($bug); } @@ -72,7 +64,7 @@ public function reduceSteps(int $bugId, int $length, int $from, int $to): void $reducer = $this->reducerManager->getReducer($this->config->getReducer()); $reducer->handle($bug, $from, $to); - $this->bugProgress->increaseProcessed($bug); + $this->bugRepository->increaseProcessed($bug); if ( $bug->getProgress()->getTotal() > 0 && $bug->getProgress()->getProcessed() === $bug->getProgress()->getTotal() @@ -84,7 +76,7 @@ public function reduceSteps(int $bugId, int $length, int $from, int $to): void public function reportBug(int $bugId): void { $bug = $this->getBug($bugId, 'report bug'); - $this->notifyHelper->notify($bug); + $this->bugNotifier->notify($bug); } /** @@ -93,18 +85,7 @@ public function reportBug(int $bugId): void public function recordVideo(int $bugId): void { $bug = $this->getBug($bugId, 'record video for bug'); - $driver = $this->selenoidHelper->createDriver($this->selenoidHelper->getCapabilities($bug, true)); - try { - foreach ($bug->getSteps() as $step) { - $this->stepRunner->run($step, $bug->getTask()->getModelRevision(), $driver); - } - } catch (ExceptionInterface $exception) { - throw $exception; - } catch (Throwable $throwable) { - // Do nothing. - } finally { - $driver->quit(); - } + $this->stepsRunner->run($bug->getSteps(), $bug, true); } public function createBug(array $steps, string $message): BugInterface @@ -119,7 +100,7 @@ public function createBug(array $steps, string $message): BugInterface protected function getBug(int $bugId, string $action): BugInterface { - $bug = $this->entityManager->find(Bug::class, $bugId); + $bug = $this->bugRepository->find($bugId); if (!$bug instanceof BugInterface) { throw new UnexpectedValueException(sprintf('Can not %s %d: bug not found', $action, $bugId)); diff --git a/src/Service/Bug/BugProgress.php b/src/Service/Bug/BugProgress.php deleted file mode 100644 index 4414ecd0..00000000 --- a/src/Service/Bug/BugProgress.php +++ /dev/null @@ -1,43 +0,0 @@ -entityManager = $entityManager; - } - - public function increaseProcessed(BugInterface $bug, int $processed = 1): void - { - $callback = function () use ($bug, $processed): void { - // Refresh the bug for the latest progress. - $this->entityManager->refresh($bug); - - $this->entityManager->lock($bug, LockMode::PESSIMISTIC_WRITE); - $bug->getProgress()->increase($processed); - }; - - $this->entityManager->transactional($callback); - } - - public function increaseTotal(BugInterface $bug, int $total): void - { - $callback = function () use ($bug, $total): void { - // Refresh the bug for the latest progress. - $this->entityManager->refresh($bug); - - $this->entityManager->lock($bug, LockMode::PESSIMISTIC_WRITE); - $bug->getProgress()->setTotal($bug->getProgress()->getTotal() + $total); - }; - - $this->entityManager->transactional($callback); - } -} diff --git a/src/Service/Bug/BugProgressInterface.php b/src/Service/Bug/BugProgressInterface.php deleted file mode 100644 index 2f44a25b..00000000 --- a/src/Service/Bug/BugProgressInterface.php +++ /dev/null @@ -1,12 +0,0 @@ -selenoidHelper = $selenoidHelper; + $this->stepRunner = $stepRunner; + } + + /** + * @throws ExceptionInterface + */ + public function run( + iterable $steps, + TaskInterface|BugInterface $entity, + bool $debug = false, + ?callable $exceptionCallback = null, + ?callable $runCallback = null + ): void { + try { + $driver = $this->selenoidHelper->createDriver($this->selenoidHelper->getCapabilities($entity, $debug)); + if ($debug) { + $this->waitForVideoContainer(); + } + foreach ($steps as $step) { + if (!$step instanceof StepInterface) { + throw new UnexpectedValueException(sprintf('Step must be instance of "%s".', StepInterface::class)); + } + $this->stepRunner->run( + $step, + ($entity instanceof BugInterface ? $entity->getTask() : $entity)->getModelRevision(), + $driver + ); + if (is_callable($runCallback)) { + if ($runCallback($step)) { + break; + } + } + } + } catch (ExceptionInterface $exception) { + throw $exception; + } catch (Throwable $throwable) { + if (is_callable($exceptionCallback)) { + $exceptionCallback($throwable, $step ?? null); + } + } finally { + if (isset($driver)) { + $driver->quit(); + } + } + } + + protected function waitForVideoContainer(): void + { + sleep(static::WAIT_SECONDS); + } +} diff --git a/src/Service/StepsRunnerInterface.php b/src/Service/StepsRunnerInterface.php new file mode 100644 index 00000000..3d51a8e1 --- /dev/null +++ b/src/Service/StepsRunnerInterface.php @@ -0,0 +1,17 @@ +generatorManager = $generatorManager; - $this->entityManager = $entityManager; - $this->stepRunner = $stepRunner; + $this->taskRepository = $taskRepository; + $this->stepsRunner = $stepsRunner; $this->bugHelper = $bugHelper; - $this->selenoidHelper = $selenoidHelper; $this->config = $config; } - /** - * @throws ExceptionInterface - */ public function run(int $taskId): void { - $task = $this->getTask($taskId); - $this->startRunning($task); + $task = $this->taskRepository->find($taskId); + + if (!$task instanceof TaskInterface) { + throw new UnexpectedValueException(sprintf('Can not run task %d: task not found', $taskId)); + } + + if ($task->isRunning()) { + throw new RuntimeException(sprintf('Can not run task %d: task is already running', $task->getId())); + } + + $this->taskRepository->startRunning($task); $steps = []; - try { - $generator = $this->generatorManager->getGenerator($this->config->getGenerator()); - $driver = $this->selenoidHelper->createDriver( - $this->selenoidHelper->getCapabilities($task, $task->isDebug()) - ); - foreach ($generator->generate($task) as $step) { + $this->stepsRunner->run( + $this->generatorManager->getGenerator($this->config->getGenerator())->generate($task), + $task, + $task->isDebug(), + function (Throwable $throwable, ?StepInterface $step) use ($task, &$steps): void { if ($step instanceof StepInterface) { - $this->stepRunner->run($step, $task->getModelRevision(), $driver); + // Last step cause the bug, we can't capture it. We capture it here. $steps[] = clone $step; } - if (count($steps) >= $this->config->getMaxSteps()) { - break; - } - } - } catch (ExceptionInterface $exception) { - throw $exception; - } catch (Throwable $throwable) { - if (isset($step) && $step instanceof StepInterface) { - // Last step cause the bug, we can't capture it. We capture it here. + $task->addBug($this->bugHelper->createBug($steps, $throwable->getMessage())); + }, + function (StepInterface $step) use (&$steps): bool { $steps[] = clone $step; - } - $task->addBug($this->bugHelper->createBug($steps, $throwable->getMessage())); - } finally { - if (isset($driver)) { - $driver->quit(); - } - $this->stopRunning($task); - } - } - protected function getTask(int $taskId): TaskInterface - { - $task = $this->entityManager->find(Task::class, $taskId); - - if (!$task instanceof TaskInterface) { - throw new UnexpectedValueException(sprintf('Can not execute task %d: task not found', $taskId)); - } - - return $task; - } - - protected function startRunning(TaskInterface $task): void - { - if ($task->isRunning()) { - throw new RuntimeException(sprintf('Task %d is already running', $task->getId())); - } else { - $task->setRunning(true); - $this->entityManager->flush(); - } - } + return count($steps) >= $this->config->getMaxSteps(); + } + ); - protected function stopRunning(TaskInterface $task): void - { - $task->setRunning(false); - // Running task take long time. Reconnect to flush changes. - $this->entityManager->getConnection()->connect(); - $this->entityManager->flush(); + $this->taskRepository->stopRunning($task); } } diff --git a/tests/MessageHandler/RunTaskMessageHandlerTest.php b/tests/MessageHandler/RunTaskMessageHandlerTest.php index 0895872e..d1c870aa 100644 --- a/tests/MessageHandler/RunTaskMessageHandlerTest.php +++ b/tests/MessageHandler/RunTaskMessageHandlerTest.php @@ -2,17 +2,17 @@ namespace Tienvx\Bundle\MbtBundle\Tests\MessageHandler; +use PHPUnit\Framework\TestCase; use Tienvx\Bundle\MbtBundle\Message\RunTaskMessage; use Tienvx\Bundle\MbtBundle\MessageHandler\RunTaskMessageHandler; use Tienvx\Bundle\MbtBundle\Service\Task\TaskHelperInterface; -use Tienvx\Bundle\MbtBundle\Tests\StepsTestCase; /** * @covers \Tienvx\Bundle\MbtBundle\MessageHandler\RunTaskMessageHandler * * @uses \Tienvx\Bundle\MbtBundle\Message\RunTaskMessage */ -class RunTaskMessageHandlerTest extends StepsTestCase +class RunTaskMessageHandlerTest extends TestCase { protected TaskHelperInterface $taskHelper; protected RunTaskMessageHandler $handler; diff --git a/tests/Reducer/HandlerTestCase.php b/tests/Reducer/HandlerTestCase.php index ed7f3ed1..f601aa11 100644 --- a/tests/Reducer/HandlerTestCase.php +++ b/tests/Reducer/HandlerTestCase.php @@ -2,62 +2,43 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Reducer; -use Doctrine\DBAL\LockMode; -use Doctrine\ORM\EntityManagerInterface; use Exception; -use Facebook\WebDriver\Remote\DesiredCapabilities; -use Facebook\WebDriver\Remote\RemoteWebDriver; +use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\MessageBusInterface; +use Throwable; use Tienvx\Bundle\MbtBundle\Entity\Bug; -use Tienvx\Bundle\MbtBundle\Entity\Task; use Tienvx\Bundle\MbtBundle\Exception\RuntimeException; use Tienvx\Bundle\MbtBundle\Message\ReduceBugMessage; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; use Tienvx\Bundle\MbtBundle\Model\BugInterface; -use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; -use Tienvx\Bundle\MbtBundle\Model\TaskInterface; use Tienvx\Bundle\MbtBundle\Reducer\HandlerInterface; -use Tienvx\Bundle\MbtBundle\Service\Bug\BugHelperInterface; -use Tienvx\Bundle\MbtBundle\Service\SelenoidHelperInterface; -use Tienvx\Bundle\MbtBundle\Service\StepRunnerInterface; +use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\StepsBuilderInterface; -use Tienvx\Bundle\MbtBundle\Tests\StepsTestCase; +use Tienvx\Bundle\MbtBundle\Service\StepsRunnerInterface; -class HandlerTestCase extends StepsTestCase +class HandlerTestCase extends TestCase { protected HandlerInterface $handler; - protected EntityManagerInterface $entityManager; + protected BugRepositoryInterface $bugRepository; protected MessageBusInterface $messageBus; - protected StepRunnerInterface $stepRunner; + protected StepsRunnerInterface $stepsRunner; protected StepsBuilderInterface $stepsBuilder; - protected BugHelperInterface $bugHelper; - protected SelenoidHelperInterface $selenoidHelper; - protected DesiredCapabilities $capabilities; - protected RemoteWebDriver $driver; protected array $newSteps; protected BugInterface $bug; - protected TaskInterface $task; - protected RevisionInterface $revision; protected function setUp(): void { - $this->entityManager = $this->createMock(EntityManagerInterface::class); + $this->bugRepository = $this->createMock(BugRepositoryInterface::class); $this->messageBus = $this->createMock(MessageBusInterface::class); - $this->stepRunner = $this->createMock(StepRunnerInterface::class); + $this->stepsRunner = $this->createMock(StepsRunnerInterface::class); $this->stepsBuilder = $this->createMock(StepsBuilderInterface::class); - $this->bugHelper = $this->createMock(BugHelperInterface::class); - $this->selenoidHelper = $this->createMock(SelenoidHelperInterface::class); $this->newSteps = [ $this->createMock(StepInterface::class), $this->createMock(StepInterface::class), $this->createMock(StepInterface::class), $this->createMock(StepInterface::class), ]; - $this->revision = $this->createMock(RevisionInterface::class); - $this->task = new Task(); - $this->task->setId(123); - $this->task->setModelRevision($this->revision); $this->bug = new Bug(); $this->bug->setId(1); $this->bug->setMessage('Something wrong'); @@ -68,116 +49,66 @@ protected function setUp(): void $this->createMock(StepInterface::class), $this->createMock(StepInterface::class), ]); - $this->task->addBug($this->bug); $this->stepsBuilder ->expects($this->once()) ->method('create') ->with($this->bug, 1, 2) ->willReturn((fn () => yield from $this->newSteps)()); - $this->driver = $this->createMock(RemoteWebDriver::class); - $this->capabilities = new DesiredCapabilities(); } public function testHandleOldBug(): void { - $this->driver->expects($this->never())->method('quit'); - $this->selenoidHelper->expects($this->never())->method('getCapabilities'); - $this->selenoidHelper->expects($this->never())->method('createDriver'); $this->bug->setSteps([ $this->createMock(StepInterface::class), $this->createMock(StepInterface::class), $this->createMock(StepInterface::class), ]); - $this->stepRunner->expects($this->never())->method('run'); + $this->stepsRunner->expects($this->never())->method('run'); $this->handler->handle($this->bug, 1, 2); } - public function testRun(): void + /** + * @dataProvider exceptionProvider + */ + public function testHandle(?Throwable $exception, bool $updateSteps): void { - $this->driver->expects($this->once())->method('quit'); - $this->selenoidHelper - ->expects($this->once()) - ->method('getCapabilities') - ->with($this->bug) - ->willReturn($this->capabilities); - $this->selenoidHelper - ->expects($this->once()) - ->method('createDriver') - ->with($this->capabilities) - ->willReturn($this->driver); - $this->stepRunner->expects($this->exactly(4)) - ->method('run')->with($this->isInstanceOf(StepInterface::class), $this->revision, $this->driver); - $this->handler->handle($this->bug, 1, 2); - } + $this->stepsRunner->expects($this->once()) + ->method('run') + ->with( + $this->newSteps, + $this->bug, + false, + $this->callback(function (callable $exceptionCallback) use ($exception) { + if ($exception) { + $exceptionCallback($exception); + } - public function testRunIntoException(): void - { - $this->driver->expects($this->once())->method('quit'); - $this->selenoidHelper - ->expects($this->once()) - ->method('getCapabilities') - ->with($this->bug) - ->willReturn($this->capabilities); - $this->selenoidHelper - ->expects($this->once()) - ->method('createDriver') - ->with($this->capabilities) - ->willReturn($this->driver); - $this->stepRunner->expects($this->exactly(4))->method('run') - ->with($this->isInstanceOf(StepInterface::class), $this->revision, $this->driver) - ->will($this->onConsecutiveCalls( - null, - null, - null, - $this->throwException(new RuntimeException('Something else wrong')), - )); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Something else wrong'); + return true; + }) + ); + if ($updateSteps) { + $this->bugRepository + ->expects($this->once()) + ->method('updateSteps') + ->with($this->bug, $this->newSteps); + $this->messageBus + ->expects($this->once()) + ->method('dispatch') + ->with($this->isInstanceOf(ReduceBugMessage::class)) + ->willReturn(new Envelope(new \stdClass())); + } else { + $this->bugRepository->expects($this->never())->method('updateSteps'); + $this->messageBus->expects($this->never())->method('dispatch'); + } $this->handler->handle($this->bug, 1, 2); } - public function testRunFoundSameBug(): void + public function exceptionProvider(): array { - $this->driver->expects($this->once())->method('quit'); - $this->selenoidHelper - ->expects($this->once()) - ->method('getCapabilities') - ->with($this->bug) - ->willReturn($this->capabilities); - $this->selenoidHelper - ->expects($this->once()) - ->method('createDriver') - ->with($this->capabilities) - ->willReturn($this->driver); - $this->entityManager->expects($this->once())->method('refresh')->with($this->bug); - $this->entityManager - ->expects($this->once()) - ->method('lock') - ->with($this->bug, LockMode::PESSIMISTIC_WRITE); - $this->entityManager - ->expects($this->once()) - ->method('transactional') - ->with($this->callback(function ($callback) { - $callback(); - - return true; - })); - $this->messageBus - ->expects($this->once()) - ->method('dispatch') - ->with($this->isInstanceOf(ReduceBugMessage::class)) - ->willReturn(new Envelope(new \stdClass())); - $this->stepRunner->expects($this->exactly(4))->method('run') - ->with($this->isInstanceOf(StepInterface::class), $this->revision, $this->driver) - ->will($this->onConsecutiveCalls( - null, - null, - null, - $this->throwException(new Exception('Something wrong')), - )); - $this->handler->handle($this->bug, 1, 2); - $this->assertSteps($this->newSteps, $this->bug->getSteps()); - $this->assertSame(0, $this->bug->getProgress()->getProcessed()); - $this->assertSame(0, $this->bug->getProgress()->getTotal()); + return [ + [null, false], + [new RuntimeException('Something else wrong'), false], + [new Exception('Something wrong'), true], + ]; } } diff --git a/tests/Reducer/Random/RandomHandlerTest.php b/tests/Reducer/Random/RandomHandlerTest.php index ae304bfc..4653018b 100644 --- a/tests/Reducer/Random/RandomHandlerTest.php +++ b/tests/Reducer/Random/RandomHandlerTest.php @@ -23,12 +23,10 @@ protected function setUp(): void { parent::setUp(); $this->handler = new RandomHandler( - $this->entityManager, + $this->bugRepository, $this->messageBus, - $this->stepRunner, - $this->stepsBuilder, - $this->bugHelper, - $this->selenoidHelper + $this->stepsRunner, + $this->stepsBuilder ); } } diff --git a/tests/Reducer/Split/SplitHandlerTest.php b/tests/Reducer/Split/SplitHandlerTest.php index 4d108448..f8f43d6d 100644 --- a/tests/Reducer/Split/SplitHandlerTest.php +++ b/tests/Reducer/Split/SplitHandlerTest.php @@ -23,12 +23,10 @@ protected function setUp(): void { parent::setUp(); $this->handler = new SplitHandler( - $this->entityManager, + $this->bugRepository, $this->messageBus, - $this->stepRunner, - $this->stepsBuilder, - $this->bugHelper, - $this->selenoidHelper + $this->stepsRunner, + $this->stepsBuilder ); } } diff --git a/tests/Repository/BugRepositoryTest.php b/tests/Repository/BugRepositoryTest.php new file mode 100644 index 00000000..b7a89409 --- /dev/null +++ b/tests/Repository/BugRepositoryTest.php @@ -0,0 +1,153 @@ +manager = $this->createMock(EntityManagerDecorator::class); + $this->manager + ->expects($this->once()) + ->method('getClassMetadata') + ->with(Bug::class) + ->willReturn($this->createMock(ClassMetadata::class)); + $managerRegistry = $this->createMock(ManagerRegistry::class); + $managerRegistry + ->expects($this->once()) + ->method('getManagerForClass') + ->with(Bug::class) + ->willReturn($this->manager); + $this->bugRepository = new BugRepository($managerRegistry); + $progress = new Progress(); + $progress->setTotal(10); + $progress->setProcessed(5); + $this->bug = new Bug(); + $this->bug->setProgress($progress); + $this->bug->setSteps([ + $this->createMock(StepInterface::class), + $this->createMock(StepInterface::class), + $this->createMock(StepInterface::class), + ]); + } + + public function testUpdateSteps(): void + { + $newSteps = [ + $this->createMock(StepInterface::class), + $this->createMock(StepInterface::class), + $this->createMock(StepInterface::class), + $this->createMock(StepInterface::class), + ]; + $this->manager->expects($this->once())->method('refresh')->with($this->bug); + $this->manager->expects($this->never())->method('lock'); + $this->manager + ->expects($this->once()) + ->method('wrapInTransaction') + ->with($this->callback(function ($callback) { + $callback(); + + return true; + })); + $this->bugRepository->updateSteps($this->bug, $newSteps); + $this->assertNotSame($newSteps, $this->bug->getSteps()); + $this->assertSame(5, $this->bug->getProgress()->getProcessed()); + $this->assertSame(10, $this->bug->getProgress()->getTotal()); + } + + public function testUpdateStepsWithShorterSteps(): void + { + $newSteps = [ + $this->createMock(StepInterface::class), + $this->createMock(StepInterface::class), + ]; + $this->manager->expects($this->once())->method('refresh')->with($this->bug); + $this->manager->expects($this->once())->method('lock')->with($this->bug, LockMode::PESSIMISTIC_WRITE); + $this->manager + ->expects($this->once()) + ->method('wrapInTransaction') + ->with($this->callback(function ($callback) { + $callback(); + + return true; + })); + $this->bugRepository->updateSteps($this->bug, $newSteps); + $this->assertSame($newSteps, $this->bug->getSteps()); + $this->assertSame(0, $this->bug->getProgress()->getProcessed()); + $this->assertSame(0, $this->bug->getProgress()->getTotal()); + } + + public function testIncreaseProcessed(): void + { + $this->manager->expects($this->once())->method('refresh')->with($this->bug); + $this->manager->expects($this->once())->method('lock')->with($this->bug, LockMode::PESSIMISTIC_WRITE); + $this->manager + ->expects($this->once()) + ->method('wrapInTransaction') + ->with($this->callback(function ($callback) { + $callback(); + + return true; + })); + $this->bugRepository->increaseProcessed($this->bug, 2); + $this->assertSame(7, $this->bug->getProgress()->getProcessed()); + $this->assertSame(10, $this->bug->getProgress()->getTotal()); + } + + public function testIncreaseProcessedReachLimit(): void + { + $this->manager->expects($this->once())->method('refresh')->with($this->bug); + $this->manager->expects($this->once())->method('lock')->with($this->bug, LockMode::PESSIMISTIC_WRITE); + $this->manager + ->expects($this->once()) + ->method('wrapInTransaction') + ->with($this->callback(function ($callback) { + $callback(); + + return true; + })); + $this->bugRepository->increaseProcessed($this->bug, 6); + $this->assertSame(10, $this->bug->getProgress()->getProcessed()); + $this->assertSame(10, $this->bug->getProgress()->getTotal()); + } + + public function testIncreaseTotal(): void + { + $this->manager->expects($this->once())->method('refresh')->with($this->bug); + $this->manager->expects($this->once())->method('lock')->with($this->bug, LockMode::PESSIMISTIC_WRITE); + $this->manager + ->expects($this->once()) + ->method('wrapInTransaction') + ->with($this->callback(function ($callback) { + $callback(); + + return true; + })); + $this->bugRepository->increaseTotal($this->bug, 3); + $this->assertSame(5, $this->bug->getProgress()->getProcessed()); + $this->assertSame(13, $this->bug->getProgress()->getTotal()); + } +} diff --git a/tests/Repository/TaskRepositoryTest.php b/tests/Repository/TaskRepositoryTest.php new file mode 100644 index 00000000..d436440e --- /dev/null +++ b/tests/Repository/TaskRepositoryTest.php @@ -0,0 +1,63 @@ +manager = $this->createMock(EntityManagerDecorator::class); + $this->manager + ->expects($this->once()) + ->method('getClassMetadata') + ->with(Task::class) + ->willReturn($this->createMock(ClassMetadata::class)); + $managerRegistry = $this->createMock(ManagerRegistry::class); + $managerRegistry + ->expects($this->once()) + ->method('getManagerForClass') + ->with(Task::class) + ->willReturn($this->manager); + $this->taskRepository = new TaskRepository($managerRegistry); + $this->task = new Task(); + } + + public function testStartRunningTask(): void + { + $this->task->setRunning(false); + $this->manager->expects($this->once())->method('flush'); + $this->taskRepository->startRunning($this->task); + $this->assertTrue($this->task->isRunning()); + } + + public function testStopRunningTask(): void + { + $connection = $this->createMock(Connection::class); + $connection->expects($this->once())->method('connect'); + $this->task->setRunning(true); + $this->manager->expects($this->once())->method('flush'); + $this->manager->expects($this->once())->method('getConnection')->willReturn($connection); + $this->taskRepository->stopRunning($this->task); + $this->assertFalse($this->task->isRunning()); + } +} diff --git a/tests/Service/Bug/BugHelperTest.php b/tests/Service/Bug/BugHelperTest.php index 4b73ed7a..b604ab54 100644 --- a/tests/Service/Bug/BugHelperTest.php +++ b/tests/Service/Bug/BugHelperTest.php @@ -2,36 +2,25 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service\Bug; -use Doctrine\DBAL\Connection; -use Doctrine\ORM\EntityManagerInterface; -use Exception; -use Facebook\WebDriver\Remote\DesiredCapabilities; -use Facebook\WebDriver\Remote\RemoteWebDriver; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\MessageBusInterface; use Tienvx\Bundle\MbtBundle\Entity\Bug; -use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; use Tienvx\Bundle\MbtBundle\Entity\Progress; -use Tienvx\Bundle\MbtBundle\Entity\Task; -use Tienvx\Bundle\MbtBundle\Exception\RuntimeException; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; use Tienvx\Bundle\MbtBundle\Message\RecordVideoMessage; use Tienvx\Bundle\MbtBundle\Message\ReportBugMessage; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; use Tienvx\Bundle\MbtBundle\Model\BugInterface; -use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; use Tienvx\Bundle\MbtBundle\Model\ProgressInterface; -use Tienvx\Bundle\MbtBundle\Model\TaskInterface; use Tienvx\Bundle\MbtBundle\Reducer\ReducerInterface; use Tienvx\Bundle\MbtBundle\Reducer\ReducerManagerInterface; +use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\Bug\BugHelper; use Tienvx\Bundle\MbtBundle\Service\Bug\BugHelperInterface; use Tienvx\Bundle\MbtBundle\Service\Bug\BugNotifierInterface; -use Tienvx\Bundle\MbtBundle\Service\Bug\BugProgressInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; -use Tienvx\Bundle\MbtBundle\Service\SelenoidHelperInterface; -use Tienvx\Bundle\MbtBundle\Service\StepRunnerInterface; +use Tienvx\Bundle\MbtBundle\Service\StepsRunnerInterface; /** * @covers \Tienvx\Bundle\MbtBundle\Service\Bug\BugHelper @@ -48,60 +37,42 @@ class BugHelperTest extends TestCase { protected ReducerManagerInterface $reducerManager; - protected EntityManagerInterface $entityManager; + protected BugRepositoryInterface $bugRepository; protected MessageBusInterface $messageBus; - protected BugProgressInterface $bugProgress; - protected BugNotifierInterface $notifyHelper; - protected StepRunnerInterface $stepRunner; - protected SelenoidHelperInterface $selenoidHelper; + protected BugNotifierInterface $bugNotifier; + protected StepsRunnerInterface $stepsRunner; protected ConfigInterface $config; protected BugHelperInterface $helper; - protected Connection $connection; - protected TaskInterface $task; protected BugInterface $bug; protected ProgressInterface $progress; - protected RevisionInterface $revision; - protected DesiredCapabilities $capabilities; - protected RemoteWebDriver $driver; protected function setUp(): void { $this->reducerManager = $this->createMock(ReducerManagerInterface::class); - $this->entityManager = $this->createMock(EntityManagerInterface::class); + $this->bugRepository = $this->createMock(BugRepositoryInterface::class); $this->messageBus = $this->createMock(MessageBusInterface::class); - $this->bugProgress = $this->createMock(BugProgressInterface::class); - $this->notifyHelper = $this->createMock(BugNotifierInterface::class); - $this->stepRunner = $this->createMock(StepRunnerInterface::class); - $this->selenoidHelper = $this->createMock(SelenoidHelperInterface::class); + $this->bugNotifier = $this->createMock(BugNotifierInterface::class); + $this->stepsRunner = $this->createMock(StepsRunnerInterface::class); $this->config = $this->createMock(ConfigInterface::class); $this->helper = new BugHelper( $this->reducerManager, - $this->entityManager, + $this->bugRepository, $this->messageBus, - $this->bugProgress, - $this->notifyHelper, - $this->stepRunner, - $this->selenoidHelper, + $this->bugNotifier, + $this->stepsRunner, $this->config ); - $this->connection = $this->createMock(Connection::class); - $this->revision = new Revision(); - $this->task = new Task(); - $this->task->setModelRevision($this->revision); $this->progress = new Progress(); $this->progress->setTotal(10); $this->progress->setProcessed(9); $this->bug = new Bug(); $this->bug->setProgress($this->progress); $this->bug->setId(123); - $this->bug->setTask($this->task); $this->bug->setSteps([ $this->createMock(StepInterface::class), $this->createMock(StepInterface::class), $this->createMock(StepInterface::class), ]); - $this->driver = $this->createMock(RemoteWebDriver::class); - $this->capabilities = new DesiredCapabilities(); } public function testCreateBug(): void @@ -122,7 +93,7 @@ public function testReduceMissingBug(): void { $this->expectException(UnexpectedValueException::class); $this->expectExceptionMessage('Can not reduce bug 123: bug not found'); - $this->entityManager->expects($this->once())->method('find')->with(Bug::class, 123)->willReturn(null); + $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn(null); $this->helper->reduceBug(123); } @@ -133,8 +104,8 @@ public function testReduceBugDispatchMessages(): void $this->config->expects($this->once())->method('getReducer')->willReturn('random'); $this->reducerManager->expects($this->once())->method('getReducer')->with('random')->willReturn($reducer); $this->messageBus->expects($this->never())->method('dispatch'); - $this->entityManager->expects($this->once())->method('find')->with(Bug::class, 123)->willReturn($this->bug); - $this->bugProgress->expects($this->once())->method('increaseTotal')->with($this->bug, 5); + $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn($this->bug); + $this->bugRepository->expects($this->once())->method('increaseTotal')->with($this->bug, 5); $this->helper->reduceBug(123); } @@ -145,12 +116,12 @@ public function testReduceBugNotDispatchMessages(): void $this->config->expects($this->once())->method('getReducer')->willReturn('random'); $this->reducerManager->expects($this->once())->method('getReducer')->with('random')->willReturn($reducer); $this->messageBus->expects($this->never())->method('dispatch'); - $this->entityManager->expects($this->once())->method('find')->with(Bug::class, 123)->willReturn($this->bug); - $this->bugProgress->expects($this->never())->method('increaseTotal'); + $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn($this->bug); + $this->bugRepository->expects($this->never())->method('increaseTotal'); $this->helper->reduceBug(123); } - public function testFinishReduceBug(): void + public function testReduceBugRecordAndReport(): void { $this->progress->setProcessed(10); $reducer = $this->createMock(ReducerInterface::class); @@ -169,8 +140,8 @@ public function testFinishReduceBug(): void && 123 === $message->getBugId(); })) ->willReturn(new Envelope(new \stdClass())); - $this->entityManager->expects($this->once())->method('find')->with(Bug::class, 123)->willReturn($this->bug); - $this->bugProgress->expects($this->never())->method('increaseTotal'); + $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn($this->bug); + $this->bugRepository->expects($this->never())->method('increaseTotal'); $this->helper->reduceBug(123); } @@ -178,24 +149,17 @@ public function testReportMissingBug(): void { $this->expectException(UnexpectedValueException::class); $this->expectExceptionMessage('Can not report bug 123: bug not found'); - $this->entityManager->expects($this->once())->method('find')->with(Bug::class, 123)->willReturn(null); + $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn(null); $this->helper->reportBug(123); } public function testReportBug(): void { - $task = new Task(); - $task->setAuthor(22); - $bug = new Bug(); - $bug->setTitle('New bug found'); - $bug->setId(123); - $bug->setMessage('Something wrong'); - $bug->setTask($task); - $this->entityManager->expects($this->once())->method('find')->with(Bug::class, 123)->willReturn($bug); - $this->notifyHelper + $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn($this->bug); + $this->bugNotifier ->expects($this->once()) ->method('notify') - ->with($bug); + ->with($this->bug); $this->helper->reportBug(123); } @@ -203,18 +167,21 @@ public function testReduceStepsMissingBug(): void { $this->expectException(UnexpectedValueException::class); $this->expectExceptionMessage('Can not reduce steps for bug 123: bug not found'); - $this->entityManager->expects($this->once())->method('find')->with(Bug::class, 123)->willReturn(null); + $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn(null); $this->helper->reduceSteps(123, 6, 1, 2); } public function testReduceReducedSteps(): void { $this->reducerManager->expects($this->never())->method('getReducer'); - $this->entityManager->expects($this->once())->method('find')->with(Bug::class, 123)->willReturn($this->bug); + $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn($this->bug); $this->helper->reduceSteps(123, 4, 1, 2); } - public function testNotStopReduceSteps(): void + /** + * @dataProvider messageProvider + */ + public function testReduceSteps(int $processed, bool $reportBug, array $messages): void { $this->bug->setSteps([ $this->createMock(StepInterface::class), @@ -226,153 +193,52 @@ public function testNotStopReduceSteps(): void $reducer->expects($this->once())->method('handle')->with($this->bug, 1, 2); $this->config->expects($this->once())->method('getReducer')->willReturn('random'); $this->reducerManager->expects($this->once())->method('getReducer')->with('random')->willReturn($reducer); - $this->messageBus->expects($this->never())->method('dispatch'); - $this->entityManager->expects($this->once())->method('find')->with(Bug::class, 123)->willReturn($this->bug); - $this->bugProgress->expects($this->once())->method('increaseProcessed')->with($this->bug, 1); - $this->helper->reduceSteps(123, 4, 1, 2); - } - - public function testStopReduceStepsAndRecordVideo(): void - { - $this->bug->setSteps([ - $this->createMock(StepInterface::class), - $this->createMock(StepInterface::class), - $this->createMock(StepInterface::class), - $this->createMock(StepInterface::class), - ]); - $reducer = $this->createMock(ReducerInterface::class); - $reducer->expects($this->once())->method('handle')->with($this->bug, 1, 2); - $this->config->expects($this->once())->method('getReducer')->willReturn('random'); - $this->reducerManager->expects($this->once())->method('getReducer')->with('random')->willReturn($reducer); - $this->messageBus - ->expects($this->exactly(1)) - ->method('dispatch') - ->with($this->callback( - fn ($message) => $message instanceof RecordVideoMessage && 123 === $message->getBugId() - )) - ->willReturn(new Envelope(new \stdClass())); - $this->entityManager->expects($this->once())->method('find')->with(Bug::class, 123)->willReturn($this->bug); - $this->bugProgress + if ($processed < $this->progress->getTotal()) { + $this->messageBus->expects($this->never())->method('dispatch'); + $this->config->expects($this->never())->method('shouldReportBug'); + } else { + $this->messageBus + ->expects($this->exactly(count($messages))) + ->method('dispatch') + ->withConsecutive(...array_map(fn (string $className) => [$this->callback( + fn ($message) => is_a($message, $className) && 123 === $message->getBugId() + )], $messages)) + ->willReturn(new Envelope(new \stdClass())); + $this->config->expects($this->once())->method('shouldReportBug')->willReturn($reportBug); + } + $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn($this->bug); + $this->bugRepository ->expects($this->once()) ->method('increaseProcessed') ->with($this->bug, 1) - ->willReturnCallback(fn () => $this->progress->setProcessed(10)); + ->willReturnCallback(fn () => $this->progress->setProcessed($processed)); $this->helper->reduceSteps(123, 4, 1, 2); } - public function testStopReduceStepsAndReportBug(): void + public function messageProvider(): array { - $this->bug->setSteps([ - $this->createMock(StepInterface::class), - $this->createMock(StepInterface::class), - $this->createMock(StepInterface::class), - $this->createMock(StepInterface::class), - ]); - $reducer = $this->createMock(ReducerInterface::class); - $reducer->expects($this->once())->method('handle')->with($this->bug, 1, 2); - $this->config->expects($this->once())->method('getReducer')->willReturn('random'); - $this->reducerManager->expects($this->once())->method('getReducer')->with('random')->willReturn($reducer); - $this->config->expects($this->once())->method('shouldReportBug')->willReturn(true); - $this->messageBus - ->expects($this->exactly(2)) - ->method('dispatch') - ->withConsecutive( - [ - $this->callback( - fn ($message) => $message instanceof RecordVideoMessage && 123 === $message->getBugId() - ), - ], - [ - $this->callback( - fn ($message) => $message instanceof ReportBugMessage && 123 === $message->getBugId() - ), - ] - ) - ->willReturn(new Envelope(new \stdClass())); - $this->entityManager->expects($this->once())->method('find')->with(Bug::class, 123)->willReturn($this->bug); - $this->bugProgress - ->expects($this->once()) - ->method('increaseProcessed') - ->with($this->bug, 1) - ->willReturnCallback(fn () => $this->progress->setProcessed(10)); - $this->helper->reduceSteps(123, 4, 1, 2); + return [ + [9, true, []], + [10, false, [RecordVideoMessage::class]], + [10, true, [RecordVideoMessage::class, ReportBugMessage::class]], + ]; } public function testRecordVideoMissingBug(): void { $this->expectException(UnexpectedValueException::class); $this->expectExceptionMessage('Can not record video for bug 123: bug not found'); - $this->entityManager->expects($this->once())->method('find')->with(Bug::class, 123)->willReturn(null); - $this->helper->recordVideo(123); - } - - public function testRecordVideoThrowException(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Exception that we care about'); - $this->driver->expects($this->once())->method('quit'); - $this->selenoidHelper - ->expects($this->once()) - ->method('getCapabilities') - ->with($this->bug, true) - ->willReturn($this->capabilities); - $this->selenoidHelper - ->expects($this->once()) - ->method('createDriver') - ->with($this->capabilities) - ->willReturn($this->driver); - $this->stepRunner - ->expects($this->once()) - ->method('run') - ->with($this->isInstanceOf(StepInterface::class), $this->revision, $this->driver) - ->willThrowException(new RuntimeException('Exception that we care about')); - $this->entityManager->expects($this->once())->method('find')->with(Bug::class, 123)->willReturn($this->bug); - $this->helper->recordVideo(123); - } - - public function testRecordVideoNotThrowException(): void - { - $this->driver->expects($this->once())->method('quit'); - $this->selenoidHelper - ->expects($this->once()) - ->method('getCapabilities') - ->with($this->bug, true) - ->willReturn($this->capabilities); - $this->selenoidHelper - ->expects($this->once()) - ->method('createDriver') - ->with($this->capabilities) - ->willReturn($this->driver); - $this->stepRunner - ->expects($this->exactly(2)) - ->method('run') - ->with($this->isInstanceOf(StepInterface::class), $this->revision, $this->driver) - ->willReturnOnConsecutiveCalls( - null, - $this->throwException(new Exception("Exception that we don't care about")), - ); - $this->entityManager->expects($this->once())->method('find')->with(Bug::class, 123)->willReturn($this->bug); + $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn(null); $this->helper->recordVideo(123); } public function testRecordVideo(): void { - $this->driver->expects($this->once())->method('quit'); - $this->selenoidHelper - ->expects($this->once()) - ->method('getCapabilities') - ->with($this->bug, true) - ->willReturn($this->capabilities); - $this->selenoidHelper + $this->stepsRunner ->expects($this->once()) - ->method('createDriver') - ->with($this->capabilities) - ->willReturn($this->driver); - $this->stepRunner - ->expects($this->exactly(3)) ->method('run') - ->with($this->isInstanceOf(StepInterface::class), $this->revision, $this->driver); - $this->entityManager->expects($this->once())->method('find')->with(Bug::class, 123)->willReturn($this->bug); + ->with($this->bug->getSteps(), $this->bug, true); + $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn($this->bug); $this->helper->recordVideo(123); } } diff --git a/tests/Service/Bug/BugProgressTest.php b/tests/Service/Bug/BugProgressTest.php deleted file mode 100644 index 52e9f9ba..00000000 --- a/tests/Service/Bug/BugProgressTest.php +++ /dev/null @@ -1,88 +0,0 @@ -entityManager = $this->createMock(EntityManagerInterface::class); - $progress = new Progress(); - $progress->setTotal(10); - $progress->setProcessed(5); - $this->bug = new Bug(); - $this->bug->setProgress($progress); - } - - public function testIncreaseProcessed(): void - { - $this->entityManager->expects($this->once())->method('refresh')->with($this->bug); - $this->entityManager->expects($this->once())->method('lock')->with($this->bug, LockMode::PESSIMISTIC_WRITE); - $this->entityManager - ->expects($this->once()) - ->method('transactional') - ->with($this->callback(function ($callback) { - $callback(); - - return true; - })); - $bugProgress = new BugProgress($this->entityManager); - $bugProgress->increaseProcessed($this->bug, 2); - $this->assertSame(7, $this->bug->getProgress()->getProcessed()); - $this->assertSame(10, $this->bug->getProgress()->getTotal()); - } - - public function testIncreaseProcessedReachLimit(): void - { - $this->entityManager->expects($this->once())->method('refresh')->with($this->bug); - $this->entityManager->expects($this->once())->method('lock')->with($this->bug, LockMode::PESSIMISTIC_WRITE); - $this->entityManager - ->expects($this->once()) - ->method('transactional') - ->with($this->callback(function ($callback) { - $callback(); - - return true; - })); - $bugProgress = new BugProgress($this->entityManager); - $bugProgress->increaseProcessed($this->bug, 6); - $this->assertSame(10, $this->bug->getProgress()->getProcessed()); - $this->assertSame(10, $this->bug->getProgress()->getTotal()); - } - - public function testIncreaseTotal(): void - { - $this->entityManager->expects($this->once())->method('refresh')->with($this->bug); - $this->entityManager->expects($this->once())->method('lock')->with($this->bug, LockMode::PESSIMISTIC_WRITE); - $this->entityManager - ->expects($this->once()) - ->method('transactional') - ->with($this->callback(function ($callback) { - $callback(); - - return true; - })); - $bugProgress = new BugProgress($this->entityManager); - $bugProgress->increaseTotal($this->bug, 3); - $this->assertSame(5, $this->bug->getProgress()->getProcessed()); - $this->assertSame(13, $this->bug->getProgress()->getTotal()); - } -} diff --git a/tests/Service/SelenoidHelperTest.php b/tests/Service/SelenoidHelperTest.php index 0031550b..237c62a7 100644 --- a/tests/Service/SelenoidHelperTest.php +++ b/tests/Service/SelenoidHelperTest.php @@ -3,6 +3,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service; use Facebook\WebDriver\Remote\DesiredCapabilities; +use PHPUnit\Framework\TestCase; use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Entity\Task; use Tienvx\Bundle\MbtBundle\Entity\Task\Browser; @@ -10,7 +11,6 @@ use Tienvx\Bundle\MbtBundle\Model\TaskInterface; use Tienvx\Bundle\MbtBundle\Service\SelenoidHelper; use Tienvx\Bundle\MbtBundle\Service\SelenoidHelperInterface; -use Tienvx\Bundle\MbtBundle\Tests\StepsTestCase; /** * @uses \Tienvx\Bundle\MbtBundle\Entity\Task @@ -20,7 +20,7 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Task\Browser * @covers \Tienvx\Bundle\MbtBundle\Service\SelenoidHelper */ -class SelenoidHelperTest extends StepsTestCase +class SelenoidHelperTest extends TestCase { protected string $webdriverUri = 'http://localhost:4444'; protected SelenoidHelperInterface $selenoidHelper; diff --git a/tests/Service/StepsRunnerTest.php b/tests/Service/StepsRunnerTest.php new file mode 100644 index 00000000..8732dd33 --- /dev/null +++ b/tests/Service/StepsRunnerTest.php @@ -0,0 +1,326 @@ +revision = new Revision(); + $this->task = new Task(); + $this->task->setModelRevision($this->revision); + $this->bug = new Bug(); + $this->bug->setTask($this->task); + } + + protected function setUp(): void + { + $this->steps = [ + new Step([], new Color(), 0), + new Step([], new Color(), 1), + new Step([], new Color(), 2), + new Step([], new Color(), 3), + ]; + $this->stepRunner = $this->createMock(StepRunnerInterface::class); + $this->selenoidHelper = $this->createMock(SelenoidHelperInterface::class); + $this->stepsRunner = $this->getMockBuilder(StepsRunner::class) + ->onlyMethods(['waitForVideoContainer']) + ->setConstructorArgs([ + $this->selenoidHelper, + $this->stepRunner, + ]) + ->getMock(); + $this->driver = $this->createMock(RemoteWebDriver::class); + $this->capabilities = new DesiredCapabilities(); + $this->exceptionCallback = $this->getMockBuilder(\stdClass::class) + ->addMethods(['__invoke']) + ->getMock(); + $this->runCallback = $this->getMockBuilder(\stdClass::class) + ->addMethods(['__invoke']) + ->getMock(); + } + + /** + * @dataProvider entityProvider + */ + public function testRunCanNotCreateDriver( + TaskInterface|BugInterface $entity, + bool $debug, + bool $hasExceptionCallback, + bool $hasRunCallback + ): void { + $exception = new Exception('can not create driver'); + $this->selenoidHelper + ->expects($this->once()) + ->method('getCapabilities') + ->with($entity, $debug) + ->willReturn($this->capabilities); + $this->selenoidHelper + ->expects($this->once()) + ->method('createDriver') + ->with($this->capabilities) + ->willThrowException($exception); + $this->driver->expects($this->never())->method('quit'); + $this->stepRunner->expects($this->never())->method('run'); + if ($hasExceptionCallback) { + $this->exceptionCallback->expects($this->once())->method('__invoke')->with($exception, null); + } else { + $this->exceptionCallback->expects($this->never())->method('__invoke'); + } + $this->runCallback->expects($this->never())->method('__invoke'); + $this->stepsRunner->run( + $this->steps, + $entity, + $debug, + $hasExceptionCallback ? $this->exceptionCallback : null, + $hasRunCallback ? $this->runCallback : null + ); + } + + /** + * @dataProvider entityProvider + */ + public function testRunInvalidSteps( + TaskInterface|BugInterface $entity, + bool $debug, + bool $hasExceptionCallback, + bool $hasRunCallback + ): void { + $this->expectExceptionObject( + new UnexpectedValueException(sprintf('Step must be instance of "%s".', StepInterface::class)) + ); + $this->selenoidHelper + ->expects($this->once()) + ->method('getCapabilities') + ->with($entity, $debug) + ->willReturn($this->capabilities); + $this->selenoidHelper + ->expects($this->once()) + ->method('createDriver') + ->with($this->capabilities) + ->willReturn($this->driver); + $this->driver->expects($this->once())->method('quit'); + $this->stepRunner->expects($this->never())->method('run'); + $this->exceptionCallback->expects($this->never())->method('__invoke'); + $this->runCallback->expects($this->never())->method('__invoke'); + $this->stepsRunner->expects($this->exactly($debug))->method('waitForVideoContainer'); + $this->stepsRunner->run( + [new \stdClass()], + $entity, + $debug, + $hasExceptionCallback ? $this->exceptionCallback : null, + $hasRunCallback ? $this->runCallback : null + ); + } + + /** + * @dataProvider entityProvider + */ + public function testRunNotFoundBugAndNotReachMaxSteps( + TaskInterface|BugInterface $entity, + bool $debug, + bool $hasExceptionCallback, + bool $hasRunCallback + ): void { + $this->selenoidHelper + ->expects($this->once()) + ->method('getCapabilities') + ->with($entity, $debug) + ->willReturn($this->capabilities); + $this->selenoidHelper + ->expects($this->once()) + ->method('createDriver') + ->with($this->capabilities) + ->willReturn($this->driver); + $this->driver->expects($this->once())->method('quit'); + $this->stepRunner + ->expects($this->exactly(count($this->steps))) + ->method('run') + ->withConsecutive(...array_map( + fn (StepInterface $step) => [$step, $this->revision, $this->driver], + $this->steps + )); + $this->exceptionCallback->expects($this->never())->method('__invoke'); + $this->runCallback + ->expects($this->exactly($hasRunCallback ? count($this->steps) : 0)) + ->method('__invoke') + ->withConsecutive(...array_map( + fn (StepInterface $step) => [$step], + $this->steps + )) + ->willReturn(false); + $this->stepsRunner->expects($this->exactly($debug))->method('waitForVideoContainer'); + $this->stepsRunner->run( + $this->steps, + $entity, + $debug, + $hasExceptionCallback ? $this->exceptionCallback : null, + $hasRunCallback ? $this->runCallback : null + ); + } + + /** + * @dataProvider entityProvider + */ + public function testRunFoundBugAtThirdStep( + TaskInterface|BugInterface $entity, + bool $debug, + bool $hasExceptionCallback, + bool $hasRunCallback + ): void { + $exception = new Exception('Can not run the third step'); + $this->selenoidHelper + ->expects($this->once()) + ->method('getCapabilities') + ->with($entity, $debug) + ->willReturn($this->capabilities); + $this->selenoidHelper + ->expects($this->once()) + ->method('createDriver') + ->with($this->capabilities) + ->willReturn($this->driver); + $this->driver->expects($this->once())->method('quit'); + $this->stepRunner + ->expects($this->exactly(3)) + ->method('run') + ->withConsecutive(...array_map( + fn (StepInterface $step) => [$step, $this->revision, $this->driver], + array_slice($this->steps, 0, 3) + )) + ->will($this->onConsecutiveCalls( + null, + null, + $this->throwException($exception), + )); + $this->exceptionCallback + ->expects($this->exactly($hasExceptionCallback)) + ->method('__invoke') + ->with($exception, $this->steps[2]); + $this->runCallback + ->expects($this->exactly($hasRunCallback ? 2 : 0)) + ->method('__invoke') + ->withConsecutive(...array_map( + fn (StepInterface $step) => [$step], + array_slice($this->steps, 0, 2) + )) + ->willReturn(false); + $this->stepsRunner->expects($this->exactly($debug))->method('waitForVideoContainer'); + $this->stepsRunner->run( + $this->steps, + $entity, + $debug, + $hasExceptionCallback ? $this->exceptionCallback : null, + $hasRunCallback ? $this->runCallback : null + ); + } + + /** + * @dataProvider entityProvider + */ + public function testRunReachMaxSteps2( + TaskInterface|BugInterface $entity, + bool $debug, + bool $hasExceptionCallback, + bool $hasRunCallback + ): void { + $this->selenoidHelper + ->expects($this->once()) + ->method('getCapabilities') + ->with($entity, $debug) + ->willReturn($this->capabilities); + $this->selenoidHelper + ->expects($this->once()) + ->method('createDriver') + ->with($this->capabilities) + ->willReturn($this->driver); + $this->driver->expects($this->once())->method('quit'); + $this->stepRunner + ->expects($this->exactly($hasRunCallback ? 2 : count($this->steps))) + ->method('run') + ->withConsecutive(...array_map( + fn (StepInterface $step) => [$step, $this->revision, $this->driver], + $hasRunCallback ? array_slice($this->steps, 0, 2) : $this->steps + )); + $this->exceptionCallback->expects($this->never())->method('__invoke'); + $this->runCallback + ->expects($this->exactly($hasRunCallback ? 2 : 0)) + ->method('__invoke') + ->withConsecutive(...array_map( + fn (StepInterface $step) => [$step], + array_slice($this->steps, 0, 2) + )) + ->willReturnOnConsecutiveCalls(false, true); + $this->stepsRunner->expects($this->exactly($debug))->method('waitForVideoContainer'); + $this->stepsRunner->run( + $this->steps, + $entity, + $debug, + $hasExceptionCallback ? $this->exceptionCallback : null, + $hasRunCallback ? $this->runCallback : null + ); + } + + public function entityProvider(): array + { + return [ + [$this->task, true, false, false], + [$this->task, true, true, false], + [$this->task, true, false, true], + [$this->task, true, true, true], + [$this->task, false, false, false], + [$this->task, false, true, false], + [$this->task, false, false, true], + [$this->task, false, true, true], + [$this->bug, true, false, false], + [$this->bug, true, true, false], + [$this->bug, true, false, true], + [$this->bug, true, true, true], + [$this->bug, false, false, false], + [$this->bug, false, true, false], + [$this->bug, false, false, true], + [$this->bug, false, true, true], + ]; + } +} diff --git a/tests/Service/Task/TaskHelperTest.php b/tests/Service/Task/TaskHelperTest.php index 69d6f2ff..f8d4b74d 100644 --- a/tests/Service/Task/TaskHelperTest.php +++ b/tests/Service/Task/TaskHelperTest.php @@ -2,28 +2,25 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service\Task; -use Doctrine\DBAL\Connection; -use Doctrine\ORM\EntityManagerInterface; use Exception; -use Facebook\WebDriver\Remote\DesiredCapabilities; -use Facebook\WebDriver\Remote\RemoteWebDriver; +use PHPUnit\Framework\TestCase; use SingleColorPetrinet\Model\Color; +use Throwable; use Tienvx\Bundle\MbtBundle\Entity\Bug; -use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; use Tienvx\Bundle\MbtBundle\Entity\Task; use Tienvx\Bundle\MbtBundle\Exception\RuntimeException; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; use Tienvx\Bundle\MbtBundle\Generator\GeneratorInterface; use Tienvx\Bundle\MbtBundle\Generator\GeneratorManagerInterface; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; +use Tienvx\Bundle\MbtBundle\Model\BugInterface; use Tienvx\Bundle\MbtBundle\Model\TaskInterface; +use Tienvx\Bundle\MbtBundle\Repository\TaskRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\Bug\BugHelperInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; -use Tienvx\Bundle\MbtBundle\Service\SelenoidHelperInterface; -use Tienvx\Bundle\MbtBundle\Service\StepRunnerInterface; +use Tienvx\Bundle\MbtBundle\Service\StepsRunnerInterface; use Tienvx\Bundle\MbtBundle\Service\Task\TaskHelper; use Tienvx\Bundle\MbtBundle\Service\Task\TaskHelperInterface; -use Tienvx\Bundle\MbtBundle\Tests\StepsTestCase; use Tienvx\Bundle\MbtBundle\ValueObject\Bug\Step; /** @@ -36,21 +33,17 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Bug * @uses \Tienvx\Bundle\MbtBundle\Model\Bug\Step */ -class TaskHelperTest extends StepsTestCase +class TaskHelperTest extends TestCase { protected array $steps; protected GeneratorManagerInterface $generatorManager; - protected EntityManagerInterface $entityManager; - protected StepRunnerInterface $stepRunner; + protected TaskRepositoryInterface $taskRepository; + protected StepsRunnerInterface $stepsRunner; protected BugHelperInterface $bugHelper; protected TaskHelperInterface $taskHelper; - protected SelenoidHelperInterface $selenoidHelper; protected ConfigInterface $config; - protected Connection $connection; - protected DesiredCapabilities $capabilities; - protected RemoteWebDriver $driver; - protected Revision $revision; protected TaskInterface $task; + protected BugInterface $bug; protected function setUp(): void { @@ -61,151 +54,109 @@ protected function setUp(): void new Step([], new Color(), 3), ]; $this->generatorManager = $this->createMock(GeneratorManagerInterface::class); - $this->entityManager = $this->createMock(EntityManagerInterface::class); - $this->stepRunner = $this->createMock(StepRunnerInterface::class); + $this->taskRepository = $this->createMock(TaskRepositoryInterface::class); + $this->stepsRunner = $this->createMock(StepsRunnerInterface::class); $this->bugHelper = $this->createMock(BugHelperInterface::class); - $this->selenoidHelper = $this->createMock(SelenoidHelperInterface::class); $this->config = $this->createMock(ConfigInterface::class); - $this->connection = $this->createMock(Connection::class); $this->taskHelper = new TaskHelper( $this->generatorManager, - $this->entityManager, - $this->stepRunner, + $this->taskRepository, + $this->stepsRunner, $this->bugHelper, - $this->selenoidHelper, $this->config ); - $this->driver = $this->createMock(RemoteWebDriver::class); - $this->capabilities = new DesiredCapabilities(); - $this->revision = new Revision(); $this->task = new Task(); $this->task->setId(123); $this->task->setRunning(false); - $this->task->setModelRevision($this->revision); + $this->task->setDebug(true); + $this->bug = new Bug(); } public function testRunNoTask(): void { $this->expectException(UnexpectedValueException::class); - $this->expectExceptionMessage('Can not execute task 123: task not found'); - $this->entityManager->expects($this->once())->method('find')->with(Task::class, 123)->willReturn(null); + $this->expectExceptionMessage('Can not run task 123: task not found'); + $this->taskRepository->expects($this->once())->method('find')->with(123)->willReturn(null); $this->taskHelper->run(123); } - public function testRunAlreadyRunningTask(): void + public function testRunTaskAlreadyRunning(): void { - $task = new Task(); - $task->setId(123); - $task->setRunning(true); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Task 123 is already running'); - $this->entityManager->expects($this->once())->method('find')->with(Task::class, 123)->willReturn($task); + $this->expectExceptionMessage('Can not run task 123: task is already running'); + $this->task->setRunning(true); + $this->taskRepository->expects($this->once())->method('find')->with(123)->willReturn($this->task); $this->taskHelper->run(123); } - public function testRun(): void + /** + * @dataProvider stepProvider + */ + public function testRun(?Throwable $exception, ?StepInterface $step): void { - $this->config->expects($this->exactly(4))->method('getMaxSteps')->willReturn(150); + $this->taskRepository->expects($this->once())->method('find')->with(123)->willReturn($this->task); + $this->taskRepository->expects($this->once())->method('startRunning')->with($this->task); + $this->taskRepository->expects($this->once())->method('stopRunning')->with($this->task); $generator = $this->createMock(GeneratorInterface::class); - $generator->expects($this->once())->method('generate')->with($this->task)->willReturnCallback( - fn () => yield from $this->steps - ); - $this->driver->expects($this->once())->method('quit'); - $this->config->expects($this->once())->method('getGenerator')->willReturn('random'); + $generator->expects($this->once())->method('generate')->with($this->task)->willReturn($this->steps); $this->generatorManager->expects($this->once())->method('getGenerator')->with('random')->willReturn($generator); - $this->selenoidHelper - ->expects($this->once()) - ->method('getCapabilities') - ->with($this->task, $this->task->isDebug()) - ->willReturn($this->capabilities); - $this->selenoidHelper + $this->config->expects($this->once())->method('getGenerator')->willReturn('random'); + $this->stepsRunner ->expects($this->once()) - ->method('createDriver') - ->with($this->capabilities) - ->willReturn($this->driver); - $this->stepRunner - ->expects($this->exactly(4)) ->method('run') - ->with($this->isInstanceOf(StepInterface::class), $this->revision, $this->driver); - $this->entityManager->expects($this->once())->method('find')->with(Task::class, 123)->willReturn($this->task); - $this->entityManager->expects($this->exactly(2))->method('flush'); - $this->connection->expects($this->once())->method('connect'); - $this->entityManager->expects($this->once())->method('getConnection')->willReturn($this->connection); - $this->entityManager->expects($this->never())->method('persist'); - $this->taskHelper->run(123); - $this->assertFalse($this->task->isRunning()); - } + ->with( + $this->steps, + $this->task, + $this->task->isDebug(), + $this->callback(function (callable $exceptionCallback) use ($exception, $step) { + if ($exception) { + $exceptionCallback($exception, $step); + } - public function testRunFoundBug(): void - { - $this->config->expects($this->exactly(2))->method('getMaxSteps')->willReturn(150); - $generator = $this->createMock(GeneratorInterface::class); - $generator->expects($this->once())->method('generate')->with($this->task)->willReturnCallback( - fn () => yield from $this->steps - ); - $this->driver->expects($this->once())->method('quit'); - $this->config->expects($this->once())->method('getGenerator')->willReturn('random'); - $this->generatorManager->expects($this->once())->method('getGenerator')->with('random')->willReturn($generator); - $this->selenoidHelper - ->expects($this->once()) - ->method('getCapabilities') - ->with($this->task, $this->task->isDebug()) - ->willReturn($this->capabilities); - $this->selenoidHelper - ->expects($this->once()) - ->method('createDriver') - ->with($this->capabilities) - ->willReturn($this->driver); - $this->stepRunner->expects($this->exactly(3))->method('run') - ->with($this->isInstanceOf(StepInterface::class), $this->revision, $this->driver) - ->will($this->onConsecutiveCalls( - null, - null, - $this->throwException(new Exception('Can not run the third step')), - )); - $this->entityManager->expects($this->once())->method('find')->with(Task::class, 123)->willReturn($this->task); - $this->entityManager->expects($this->exactly(2))->method('flush'); - $this->connection->expects($this->once())->method('connect'); - $this->entityManager->expects($this->once())->method('getConnection')->willReturn($this->connection); - $this->bugHelper - ->expects($this->once()) - ->method('createBug') - ->with([$this->steps[0], $this->steps[1], $this->steps[2]], 'Can not run the third step') - ->willReturn($bug = new Bug()); + return true; + }), + $this->callback(function (callable $runCallback) use ($exception, $step) { + if (!$exception && $step) { + $this->assertFalse($runCallback($step)); + } + return true; + }) + ); + if ($exception) { + $this->bugHelper + ->expects($this->once()) + ->method('createBug') + ->with($step ? [$step] : [], $exception->getMessage()) + ->willReturn($this->bug); + } else { + $this->bugHelper->expects($this->never())->method('createBug'); + } + if (!$exception && $step) { + $this->config + ->expects($this->once()) + ->method('getMaxSteps') + ->willReturn(150); + } else { + $this->config->expects($this->never())->method('getMaxSteps'); + } $this->taskHelper->run(123); - $this->assertFalse($this->task->isRunning()); - $this->assertSame([$bug], $this->task->getBugs()->toArray()); + if ($exception) { + $this->assertSame([$this->bug], $this->task->getBugs()->toArray()); + } else { + $this->assertEmpty($this->task->getBugs()); + } } - public function testRunReachMaxSteps(): void + public function stepProvider(): array { - $this->config->expects($this->exactly(2))->method('getMaxSteps')->willReturn(2); - $generator = $this->createMock(GeneratorInterface::class); - $generator->expects($this->once())->method('generate')->with($this->task)->willReturnCallback( - fn () => yield from $this->steps - ); - $this->driver->expects($this->once())->method('quit'); - $this->config->expects($this->once())->method('getGenerator')->willReturn('random'); - $this->generatorManager->expects($this->once())->method('getGenerator')->with('random')->willReturn($generator); - $this->selenoidHelper - ->expects($this->once()) - ->method('getCapabilities') - ->with($this->task, $this->task->isDebug()) - ->willReturn($this->capabilities); - $this->selenoidHelper - ->expects($this->once()) - ->method('createDriver') - ->with($this->capabilities) - ->willReturn($this->driver); - $this->stepRunner->expects($this->exactly(2))->method('run') - ->with($this->isInstanceOf(StepInterface::class), $this->revision, $this->driver); - $this->entityManager->expects($this->once())->method('find')->with(Task::class, 123)->willReturn($this->task); - $this->entityManager->expects($this->exactly(2))->method('flush'); - $this->connection->expects($this->once())->method('connect'); - $this->entityManager->expects($this->once())->method('getConnection')->willReturn($this->connection); - $this->entityManager->expects($this->never())->method('persist'); - $this->taskHelper->run(123); - $this->assertFalse($this->task->isRunning()); + $step = new Step([], new Color(), 0); + + return [ + [null, null], + [null, $step], + [new Exception('Something wrong'), null], + [new Exception('Caught a bug'), $step], + ]; } } diff --git a/tests/StepsTestCase.php b/tests/StepsTestCase.php deleted file mode 100644 index 7a236386..00000000 --- a/tests/StepsTestCase.php +++ /dev/null @@ -1,24 +0,0 @@ - $step) { - $this->assertInstanceOf(StepInterface::class, $step); - $this->assertStep($expected[$index], $step); - } - } - - public function assertStep(StepInterface $expected, StepInterface $actual): void - { - $this->assertSame($expected->getPlaces(), $actual->getPlaces()); - $this->assertSame($expected->getColor()->getValues(), $actual->getColor()->getValues()); - $this->assertSame($expected->getTransition(), $actual->getTransition()); - } -} From 5f3ecfe1e42f60cac04cf06c1705e761d9effe19 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 9 Apr 2022 08:46:18 +0700 Subject: [PATCH 02/85] Fix unit tests coverage not updated --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e19a0e26..5b74e9ce 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,4 +42,4 @@ jobs: run: | composer global require php-coveralls/php-coveralls php-coveralls --coverage_clover=clover.xml -v - if: matrix.php-versions == '7.4' + if: matrix.php-versions == '8.0' From b4ea83b2df4130b83a9c12f3c023da682971c818 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 9 Apr 2022 08:48:25 +0700 Subject: [PATCH 03/85] Set video frame rate to 60 --- src/Service/SelenoidHelper.php | 1 + tests/Service/SelenoidHelperTest.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Service/SelenoidHelper.php b/src/Service/SelenoidHelper.php index 14747238..ad22ea39 100644 --- a/src/Service/SelenoidHelper.php +++ b/src/Service/SelenoidHelper.php @@ -52,6 +52,7 @@ public function getCapabilities(TaskInterface|BugInterface $entity, bool $debug $caps += [ 'logName' => $this->getLogName($entity), 'videoName' => $this->getVideoName($entity), + 'videoFrameRate' => 60, ]; } diff --git a/tests/Service/SelenoidHelperTest.php b/tests/Service/SelenoidHelperTest.php index 237c62a7..2e0e2e2d 100644 --- a/tests/Service/SelenoidHelperTest.php +++ b/tests/Service/SelenoidHelperTest.php @@ -93,6 +93,7 @@ private function assertCapabilities(DesiredCapabilities $capabilities, bool $deb $debug ? ($hasBug ? "bug-{$this->bug->getId()}.mp4" : "task-{$this->task->getId()}.mp4") : null, $capabilities->getCapability('videoName') ); + $this->assertSame($debug ? 60 : null, $capabilities->getCapability('videoFrameRate')); } public function capabilitiesParameterProvider(): array From 92cb4ef11c43049c7d11798ababe702a5dcfbb1b Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 9 Apr 2022 08:53:22 +0700 Subject: [PATCH 04/85] Separate @covers and @uses --- tests/Service/SelenoidHelperTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Service/SelenoidHelperTest.php b/tests/Service/SelenoidHelperTest.php index 2e0e2e2d..cae4d0c0 100644 --- a/tests/Service/SelenoidHelperTest.php +++ b/tests/Service/SelenoidHelperTest.php @@ -13,12 +13,13 @@ use Tienvx\Bundle\MbtBundle\Service\SelenoidHelperInterface; /** + * @covers \Tienvx\Bundle\MbtBundle\Service\SelenoidHelper + * * @uses \Tienvx\Bundle\MbtBundle\Entity\Task * @uses \Tienvx\Bundle\MbtBundle\Model\Task * @uses \Tienvx\Bundle\MbtBundle\Entity\Bug * @uses \Tienvx\Bundle\MbtBundle\Model\Bug * @uses \Tienvx\Bundle\MbtBundle\Model\Task\Browser - * @covers \Tienvx\Bundle\MbtBundle\Service\SelenoidHelper */ class SelenoidHelperTest extends TestCase { From 3ddf246fa5a396925c6fe9bdfd4dc2a1de1dc336 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 9 Apr 2022 09:01:58 +0700 Subject: [PATCH 05/85] Add cs step in readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 23acd560..8096bd66 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ vendor/bin/phpunit ```shell phpcs --standard=PSR12 src tests +php-cs-fixer fix --diff --dry-run phpstan analyse src tests ``` From 6c367fb2ad2654d3bf5d951ba704530f2f317874 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 9 Apr 2022 09:19:32 +0700 Subject: [PATCH 06/85] Test invalid ranges --- .../Service/ShortestPathStepsBuilderTest.php | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/tests/Service/ShortestPathStepsBuilderTest.php b/tests/Service/ShortestPathStepsBuilderTest.php index cff89022..01be8a9b 100644 --- a/tests/Service/ShortestPathStepsBuilderTest.php +++ b/tests/Service/ShortestPathStepsBuilderTest.php @@ -6,6 +6,7 @@ use Petrinet\Model\PetrinetInterface; use Petrinet\Model\PlaceInterface; use Petrinet\Model\TransitionInterface; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SingleColorPetrinet\Builder\SingleColorPetrinetBuilder; use SingleColorPetrinet\Model\Color; @@ -17,6 +18,7 @@ use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; use Tienvx\Bundle\MbtBundle\Entity\Task; +use Tienvx\Bundle\MbtBundle\Exception\OutOfRangeException; use Tienvx\Bundle\MbtBundle\Model\Bug\Step; use Tienvx\Bundle\MbtBundle\Service\AStar\PetrinetDomainLogic; use Tienvx\Bundle\MbtBundle\Service\Petrinet\MarkingHelper; @@ -44,7 +46,7 @@ class ShortestPathStepsBuilderTest extends TestCase protected PetrinetInterface $petrinet; protected PetrinetDomainLogic $petrinetDomainLogic; protected ShortestPathStepsBuilder $stepsBuilder; - protected PetrinetHelperInterface $petrinetHelper; + protected PetrinetHelperInterface|MockObject $petrinetHelper; protected Revision $revision; protected Bug $bug; protected PlaceInterface $cartEmpty; @@ -201,16 +203,42 @@ protected function geColor(int $number): Color protected function initStepsBuilder(): void { $this->petrinetHelper = $this->createMock(PetrinetHelperInterface::class); + $this->stepsBuilder = new ShortestPathStepsBuilder($this->petrinetHelper, $this->petrinetDomainLogic); + } + + protected function expectsPetrinetHelper(): void + { $this->petrinetHelper ->expects($this->once()) ->method('build') ->with($this->revision) ->willReturn($this->petrinet); - $this->stepsBuilder = new ShortestPathStepsBuilder($this->petrinetHelper, $this->petrinetDomainLogic); + } + + /** + * @dataProvider invalidRangeProvider + */ + public function testGetInvalidRange(int $from, int $to): void + { + $this->expectExceptionObject(new OutOfRangeException('Can not create new steps using invalid range')); + iterator_to_array($this->stepsBuilder->create($this->bug, $from, $to)); + } + + public function invalidRangeProvider(): array + { + $validMinFrom = 0; + $validMaxTo = 16; + + return [ + [-1, $validMaxTo], + [$validMinFrom, 17], + [-1, 17], + ]; } public function testGetShortestPathFromCartEmptyToCheckout(): void { + $this->expectsPetrinetHelper(); $nodes = $this->stepsBuilder->create($this->bug, 0, 12); $this->assertNodes([ [ @@ -253,6 +281,7 @@ public function testGetShortestPathFromCartEmptyToCheckout(): void public function testGetShortestPathFromCartHasProductsToShipping(): void { + $this->expectsPetrinetHelper(); $nodes = $this->stepsBuilder->create($this->bug, 4, 14); $this->assertNodes([ [ From e6d18db8420df894ed4360a8af03bf00ca58d854 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 9 Apr 2022 09:24:09 +0700 Subject: [PATCH 07/85] Rename parameter to make it easier to understand --- tests/Service/ShortestPathStepsBuilderTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Service/ShortestPathStepsBuilderTest.php b/tests/Service/ShortestPathStepsBuilderTest.php index 01be8a9b..7a5333eb 100644 --- a/tests/Service/ShortestPathStepsBuilderTest.php +++ b/tests/Service/ShortestPathStepsBuilderTest.php @@ -195,9 +195,9 @@ protected function initBug(): void $this->bug->setTask($task); } - protected function geColor(int $number): Color + protected function geColor(int $products): Color { - return new Color(['products' => $number]); + return new Color(['products' => $products]); } protected function initStepsBuilder(): void From 3a6eaf1e905575ebbd2ba9a0aabeddae73cd7fb1 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 9 Apr 2022 11:19:05 +0700 Subject: [PATCH 08/85] Test PetrinetDomainLogic --- src/Service/AStar/PetrinetDomainLogic.php | 21 ++- .../Service/AStar/PetrinetDomainLogicTest.php | 177 ++++++++++++++++++ 2 files changed, 191 insertions(+), 7 deletions(-) create mode 100644 tests/Service/AStar/PetrinetDomainLogicTest.php diff --git a/src/Service/AStar/PetrinetDomainLogic.php b/src/Service/AStar/PetrinetDomainLogic.php index 287c028c..be6333fd 100644 --- a/src/Service/AStar/PetrinetDomainLogic.php +++ b/src/Service/AStar/PetrinetDomainLogic.php @@ -4,7 +4,6 @@ use Petrinet\Model\PetrinetInterface; use Petrinet\Model\TransitionInterface; -use SingleColorPetrinet\Model\ColorfulMarkingInterface; use SingleColorPetrinet\Service\GuardedTransitionServiceInterface; use Tienvx\Bundle\MbtBundle\Exception\RuntimeException; use Tienvx\Bundle\MbtBundle\Model\Bug\Step; @@ -29,18 +28,26 @@ public function setPetrinet(?PetrinetInterface $petrinet): void $this->petrinet = $petrinet; } - public function calculateEstimatedCost(mixed $fromNode, mixed $toNode): float + public function calculateEstimatedCost(mixed $fromNode, mixed $toNode): float|int { if (!$fromNode instanceof Step || !$toNode instanceof Step) { throw new RuntimeException('The provided nodes are invalid'); } - $tokensDiff = []; - foreach ($toNode->getPlaces() as $place => $tokens) { - $tokensDiff[$place] = abs($tokens - ($fromNode->getPlaces()[$place] ?? 0)); + $tokensDiff = 0; + $fromPlaces = array_keys($fromNode->getPlaces()); + $toPlaces = array_keys($toNode->getPlaces()); + foreach (array_unique(array_merge($fromPlaces, $toPlaces)) as $place) { + if (!in_array($place, $fromPlaces)) { + $tokensDiff += $toNode->getPlaces()[$place]; + } elseif (!in_array($place, $toPlaces)) { + $tokensDiff += $fromNode->getPlaces()[$place]; + } else { + $tokensDiff += abs($toNode->getPlaces()[$place] - $fromNode->getPlaces()[$place]); + } } // Estimate it will took N transitions to move N tokens if color is the same, twice if color is not the same. - return array_sum($tokensDiff) * (($fromNode->getColor()->getValues() != $toNode->getColor()->getValues()) + 1); + return $tokensDiff * (($fromNode->getColor()->getValues() != $toNode->getColor()->getValues()) + 1); } public function calculateRealCost(mixed $node, mixed $adjacent): float|int @@ -64,7 +71,7 @@ public function getAdjacentNodes(mixed $node): iterable foreach ($this->transitionService->getEnabledTransitions($this->petrinet, $marking) as $transition) { // Create new marking. $marking = $this->markingHelper->getMarking($this->petrinet, $node->getPlaces(), $node->getColor()); - if ($transition instanceof TransitionInterface && $marking instanceof ColorfulMarkingInterface) { + if ($transition instanceof TransitionInterface) { $this->transitionService->fire($transition, $marking); $adjacents[] = new Step( $this->markingHelper->getPlaces($marking), diff --git a/tests/Service/AStar/PetrinetDomainLogicTest.php b/tests/Service/AStar/PetrinetDomainLogicTest.php new file mode 100644 index 00000000..a076cbc5 --- /dev/null +++ b/tests/Service/AStar/PetrinetDomainLogicTest.php @@ -0,0 +1,177 @@ +transitionService = $this->createMock(GuardedTransitionServiceInterface::class); + $this->markingHelper = $this->createMock(MarkingHelperInterface::class); + $this->petrinetDomainLogic = new PetrinetDomainLogic($this->transitionService, $this->markingHelper); + $this->petrinet = $this->createMock(PetrinetInterface::class); + $this->transitions = [ + $transition1 = new GuardedTransition(), + $transition2 = new GuardedTransition(), + $transition3 = new GuardedTransition(), + $transition4 = new GuardedTransition(), + ]; + $transition1->setId(0); + $transition2->setId(1); + $transition3->setId(2); + $transition4->setId(3); + $this->markings = [ + $marking1 = new ColorfulMarking(), + $marking2 = new ColorfulMarking(), + $marking3 = new ColorfulMarking(), + $marking4 = new ColorfulMarking(), + $marking5 = new ColorfulMarking(), + ]; + $marking2->setColor(new Color(['key 1' => 'value 1'])); + $marking3->setColor(new Color(['key 2' => 'value 2'])); + $marking4->setColor(new Color(['key 3' => 'value 3'])); + $marking5->setColor(new Color(['key 4' => 'value 4'])); + $this->places = [ + [1 => 11], + [2 => 22], + [3 => 33], + [4 => 44], + ]; + } + + /** + * @dataProvider invalidNodeProvider + */ + public function testCalculateEstimatedCostInvalidNode(mixed $fromNode, mixed $toNode): void + { + $this->expectExceptionObject(new RuntimeException('The provided nodes are invalid')); + $this->petrinetDomainLogic->calculateEstimatedCost($fromNode, $toNode); + } + + public function invalidNodeProvider(): array + { + $step = new Step([], new Color(), 0); + + return [ + [$step, 'to node'], + ['from node', $step], + ['from node', 'to node'], + ]; + } + + /** + * @dataProvider estimatedCostProvider + */ + public function testCalculateEstimatedCost(Step $fromNode, Step $toNode, int $cost): void + { + $this->assertSame($cost, $this->petrinetDomainLogic->calculateEstimatedCost($fromNode, $toNode)); + } + + public function estimatedCostProvider(): array + { + $color = new Color(['key' => 'value']); + $differentColor = new Color(['different key' => 'different value']); + + return [ + [$this->getStep([0 => 1, 1 => 5, 2 => 3], $color), $this->getStep([], $color), 9], + [$this->getStep([], $color), $this->getStep([0 => 3, 1 => 2, 2 => 2, 3 => 1], $color), 8], + [$this->getStep([0 => 2, 1 => 4], $color), $this->getStep([1 => 5, 2 => 3, 3 => 1], $color), 7], + [$this->getStep([0 => 4, 1 => 1, 2 => 2], $color), $this->getStep([2 => 5], $differentColor), 16], + [$this->getStep([], $color), $this->getStep([0 => 4, 1 => 1], $differentColor), 10], + [ + $this->getStep([0 => 3, 1 => 2, 2 => 2], $color), + $this->getStep([1 => 7, 2 => 2, 3 => 3], $differentColor), + 22, + ], + ]; + } + + public function testCalculateRealCost(): void + { + $this->assertSame(1, $this->petrinetDomainLogic->calculateRealCost('node', 'adjacent')); + } + + public function testGetAdjacentNodesOfInvalidNode(): void + { + $this->expectExceptionObject(new RuntimeException('The provided node is invalid')); + $this->petrinetDomainLogic->getAdjacentNodes('invalid'); + } + + public function testGetAdjacentNodesWithoutPetrinet(): void + { + $this->expectExceptionObject(new RuntimeException('Petrinet is required')); + $this->petrinetDomainLogic->getAdjacentNodes($this->getStep([], new Color())); + } + + public function testGetAdjacentNodes(): void + { + $node = $this->getStep([12 => 34], new Color(['key' => 'value'])); + $this->petrinetDomainLogic->setPetrinet($this->petrinet); + $this->markingHelper + ->expects($this->exactly(count($this->transitions) + 1)) + ->method('getMarking') + ->with($this->petrinet, $node->getPlaces(), $node->getColor()) + ->willReturnOnConsecutiveCalls(...$this->markings); + $this->transitionService + ->expects($this->once()) + ->method('getEnabledTransitions') + ->with($this->petrinet, $this->markings[0]) + ->willReturn($this->transitions); + $this->transitionService + ->expects($this->exactly(count($this->transitions))) + ->method('fire') + ->withConsecutive(...array_map( + fn (int $index) => [$this->transitions[$index], $this->markings[$index + 1]], + array_keys($this->transitions) + )); + $this->markingHelper + ->expects($this->exactly(count($this->transitions))) + ->method('getPlaces') + ->withConsecutive(...array_map( + fn (MarkingInterface $marking) => [$marking], + array_slice($this->markings, 1, count($this->transitions)) + )) + ->willReturnOnConsecutiveCalls(...$this->places); + $adjacents = $this->petrinetDomainLogic->getAdjacentNodes($node); + $this->assertCount(count($this->transitions), $adjacents); + foreach ($adjacents as $index => $adjacent) { + $this->assertInstanceOf(StepInterface::class, $adjacent); + $this->assertSame($this->places[$index], $adjacent->getPlaces()); + $this->assertSame($this->markings[$index + 1]->getColor(), $adjacent->getColor()); + $this->assertSame($this->transitions[$index]->getId(), $adjacent->getTransition()); + } + } + + protected function getStep(array $places, ColorInterface $color): StepInterface + { + return new Step($places, $color, 0); + } +} From 776e50971593e2686d4e9854ea7ddbffe840c7ed Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 9 Apr 2022 11:24:13 +0700 Subject: [PATCH 09/85] Test reducer isSupported() --- tests/Reducer/Random/RandomReducerTest.php | 5 +++++ tests/Reducer/Split/SplitReducerTest.php | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/tests/Reducer/Random/RandomReducerTest.php b/tests/Reducer/Random/RandomReducerTest.php index 165ddda5..4d7066db 100644 --- a/tests/Reducer/Random/RandomReducerTest.php +++ b/tests/Reducer/Random/RandomReducerTest.php @@ -39,6 +39,11 @@ public function testGetName(): void $this->assertSame('random', RandomReducer::getName()); } + public function testIsSupported(): void + { + $this->assertTrue(RandomReducer::isSupported()); + } + public function testDispatch(): void { $bug = new Bug(); diff --git a/tests/Reducer/Split/SplitReducerTest.php b/tests/Reducer/Split/SplitReducerTest.php index 400a55e2..63fb9e32 100644 --- a/tests/Reducer/Split/SplitReducerTest.php +++ b/tests/Reducer/Split/SplitReducerTest.php @@ -39,6 +39,11 @@ public function testGetName(): void $this->assertSame('split', SplitReducer::getName()); } + public function testIsSupported(): void + { + $this->assertTrue(SplitReducer::isSupported()); + } + public function testDispatch(): void { $bug = new Bug(); From 520186d5c0c9cab158c9be0e1b335c436a671f75 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 9 Apr 2022 21:15:27 +0700 Subject: [PATCH 10/85] Refactor plugin manager tests --- src/Channel/ChannelManager.php | 19 ++-- src/Generator/GeneratorManager.php | 19 ++-- ...actPluginManager.php => PluginManager.php} | 18 +++- src/Reducer/ReducerManager.php | 19 ++-- tests/Channel/ChannelManagerTest.php | 33 ++++--- .../Compiler/PluginPassTest.php | 2 +- tests/Fixtures/Plugin/Manager1.php | 4 +- tests/Fixtures/Plugin/Manager2.php | 4 +- tests/Generator/GeneratorManagerTest.php | 46 +++------- tests/Plugin/PluginManagerTest.php | 87 +++++++++++++++++++ tests/Reducer/ReducerManagerTest.php | 52 +++-------- 11 files changed, 182 insertions(+), 121 deletions(-) rename src/Plugin/{AbstractPluginManager.php => PluginManager.php} (57%) create mode 100644 tests/Plugin/PluginManagerTest.php diff --git a/src/Channel/ChannelManager.php b/src/Channel/ChannelManager.php index 86d88741..841c8cbd 100644 --- a/src/Channel/ChannelManager.php +++ b/src/Channel/ChannelManager.php @@ -2,17 +2,22 @@ namespace Tienvx\Bundle\MbtBundle\Channel; -use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; -use Tienvx\Bundle\MbtBundle\Plugin\AbstractPluginManager; +use Tienvx\Bundle\MbtBundle\Plugin\PluginManager; -class ChannelManager extends AbstractPluginManager implements ChannelManagerInterface +class ChannelManager extends PluginManager implements ChannelManagerInterface { public function getChannel(string $name): ChannelInterface { - if ($this->has($name) && ($channel = $this->get($name)) && $channel instanceof ChannelInterface) { - return $channel; - } + return parent::get($name); + } - throw new UnexpectedValueException(sprintf('Channel "%s" does not exist.', $name)); + protected function getPluginInterface(): string + { + return ChannelInterface::class; + } + + protected function getInvalidPluginExceptionMessage(string $name): string + { + return sprintf('Channel "%s" does not exist.', $name); } } diff --git a/src/Generator/GeneratorManager.php b/src/Generator/GeneratorManager.php index 1721ccf5..c56bbe23 100644 --- a/src/Generator/GeneratorManager.php +++ b/src/Generator/GeneratorManager.php @@ -2,17 +2,22 @@ namespace Tienvx\Bundle\MbtBundle\Generator; -use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; -use Tienvx\Bundle\MbtBundle\Plugin\AbstractPluginManager; +use Tienvx\Bundle\MbtBundle\Plugin\PluginManager; -class GeneratorManager extends AbstractPluginManager implements GeneratorManagerInterface +class GeneratorManager extends PluginManager implements GeneratorManagerInterface { public function getGenerator(string $name): GeneratorInterface { - if ($this->has($name) && ($generator = $this->get($name)) && $generator instanceof GeneratorInterface) { - return $generator; - } + return parent::get($name); + } - throw new UnexpectedValueException(sprintf('Generator "%s" does not exist.', $name)); + protected function getPluginInterface(): string + { + return GeneratorInterface::class; + } + + protected function getInvalidPluginExceptionMessage(string $name): string + { + return sprintf('Generator "%s" does not exist.', $name); } } diff --git a/src/Plugin/AbstractPluginManager.php b/src/Plugin/PluginManager.php similarity index 57% rename from src/Plugin/AbstractPluginManager.php rename to src/Plugin/PluginManager.php index bc41e53a..b0a124a7 100644 --- a/src/Plugin/AbstractPluginManager.php +++ b/src/Plugin/PluginManager.php @@ -5,7 +5,7 @@ use Symfony\Component\DependencyInjection\ServiceLocator; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; -abstract class AbstractPluginManager implements PluginManagerInterface +class PluginManager implements PluginManagerInterface { protected ServiceLocator $locator; protected array $plugins; @@ -28,11 +28,21 @@ public function has(string $name): bool public function get(string $name): PluginInterface { - $plugin = $this->locator->has($name) ? $this->locator->get($name) : null; - if ($plugin instanceof PluginInterface) { + $plugin = $this->has($name) ? $this->locator->get($name) : null; + if (is_a($plugin, $this->getPluginInterface())) { return $plugin; } - throw new UnexpectedValueException(sprintf('Plugin "%s" does not exist.', $name)); + throw new UnexpectedValueException($this->getInvalidPluginExceptionMessage($name)); + } + + protected function getInvalidPluginExceptionMessage(string $name): string + { + return sprintf('Plugin "%s" does not exist.', $name); + } + + protected function getPluginInterface(): string + { + return PluginInterface::class; } } diff --git a/src/Reducer/ReducerManager.php b/src/Reducer/ReducerManager.php index 26b80ef8..5957d1a9 100644 --- a/src/Reducer/ReducerManager.php +++ b/src/Reducer/ReducerManager.php @@ -2,17 +2,22 @@ namespace Tienvx\Bundle\MbtBundle\Reducer; -use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; -use Tienvx\Bundle\MbtBundle\Plugin\AbstractPluginManager; +use Tienvx\Bundle\MbtBundle\Plugin\PluginManager; -class ReducerManager extends AbstractPluginManager implements ReducerManagerInterface +class ReducerManager extends PluginManager implements ReducerManagerInterface { public function getReducer(string $name): ReducerInterface { - if ($this->has($name) && ($reducer = $this->get($name)) && $reducer instanceof ReducerInterface) { - return $reducer; - } + return $this->get($name); + } - throw new UnexpectedValueException(sprintf('Reducer "%s" does not exist.', $name)); + protected function getPluginInterface(): string + { + return ReducerInterface::class; + } + + protected function getInvalidPluginExceptionMessage(string $name): string + { + return sprintf('Reducer "%s" does not exist.', $name); } } diff --git a/tests/Channel/ChannelManagerTest.php b/tests/Channel/ChannelManagerTest.php index c527ff48..3d6a5ce4 100644 --- a/tests/Channel/ChannelManagerTest.php +++ b/tests/Channel/ChannelManagerTest.php @@ -2,33 +2,32 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Channel; -use PHPUnit\Framework\TestCase; -use Symfony\Component\DependencyInjection\ServiceLocator; +use Tienvx\Bundle\MbtBundle\Channel\ChannelInterface; use Tienvx\Bundle\MbtBundle\Channel\ChannelManager; -use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; +use Tienvx\Bundle\MbtBundle\Tests\Plugin\PluginManagerTest; /** * @covers \Tienvx\Bundle\MbtBundle\Channel\ChannelManager - * @covers \Tienvx\Bundle\MbtBundle\Plugin\AbstractPluginManager + * + * @uses \Tienvx\Bundle\MbtBundle\Plugin\PluginManager */ -class ChannelManagerTest extends TestCase +class ChannelManagerTest extends PluginManagerTest { - protected ChannelManager $channelManager; - protected ServiceLocator $locator; + protected array $plugins = ['email', 'slack/chat']; + protected string $getMethod = 'getChannel'; - protected function setUp(): void + protected function getPluginManagerClass(): string { - $this->locator = $this->createMock(ServiceLocator::class); - $plugins = ['split', 'random']; - $this->channelManager = new ChannelManager($this->locator, $plugins); + return ChannelManager::class; } - public function testDoesNotHaveOther(): void + protected function getPluginInterface(): string { - $this->locator->expects($this->never())->method('has'); - $this->assertFalse($this->channelManager->has('other')); - $this->expectException(UnexpectedValueException::class); - $this->expectExceptionMessage('Channel "other" does not exist.'); - $this->channelManager->getChannel('other'); + return ChannelInterface::class; + } + + protected function getInvalidPluginExceptionMessage(string $plugin): string + { + return sprintf('Channel "%s" does not exist.', $plugin); } } diff --git a/tests/DependencyInjection/Compiler/PluginPassTest.php b/tests/DependencyInjection/Compiler/PluginPassTest.php index 06e6f7de..dd6e59f9 100644 --- a/tests/DependencyInjection/Compiler/PluginPassTest.php +++ b/tests/DependencyInjection/Compiler/PluginPassTest.php @@ -16,7 +16,7 @@ /** * @covers \Tienvx\Bundle\MbtBundle\DependencyInjection\Compiler\PluginPass - * @covers \Tienvx\Bundle\MbtBundle\Plugin\AbstractPluginManager + * @covers \Tienvx\Bundle\MbtBundle\Plugin\PluginManager */ class PluginPassTest extends TestCase { diff --git a/tests/Fixtures/Plugin/Manager1.php b/tests/Fixtures/Plugin/Manager1.php index 339e3fc3..4f2c8b64 100644 --- a/tests/Fixtures/Plugin/Manager1.php +++ b/tests/Fixtures/Plugin/Manager1.php @@ -2,8 +2,8 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Fixtures\Plugin; -use Tienvx\Bundle\MbtBundle\Plugin\AbstractPluginManager; +use Tienvx\Bundle\MbtBundle\Plugin\PluginManager; -class Manager1 extends AbstractPluginManager +class Manager1 extends PluginManager { } diff --git a/tests/Fixtures/Plugin/Manager2.php b/tests/Fixtures/Plugin/Manager2.php index 2a53c533..2e684259 100644 --- a/tests/Fixtures/Plugin/Manager2.php +++ b/tests/Fixtures/Plugin/Manager2.php @@ -2,8 +2,8 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Fixtures\Plugin; -use Tienvx\Bundle\MbtBundle\Plugin\AbstractPluginManager; +use Tienvx\Bundle\MbtBundle\Plugin\PluginManager; -class Manager2 extends AbstractPluginManager +class Manager2 extends PluginManager { } diff --git a/tests/Generator/GeneratorManagerTest.php b/tests/Generator/GeneratorManagerTest.php index 03ce83b1..ec1268cf 100644 --- a/tests/Generator/GeneratorManagerTest.php +++ b/tests/Generator/GeneratorManagerTest.php @@ -2,54 +2,32 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Generator; -use PHPUnit\Framework\TestCase; -use Symfony\Component\DependencyInjection\ServiceLocator; -use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; use Tienvx\Bundle\MbtBundle\Generator\GeneratorInterface; use Tienvx\Bundle\MbtBundle\Generator\GeneratorManager; +use Tienvx\Bundle\MbtBundle\Tests\Plugin\PluginManagerTest; /** * @covers \Tienvx\Bundle\MbtBundle\Generator\GeneratorManager - * @covers \Tienvx\Bundle\MbtBundle\Plugin\AbstractPluginManager + * + * @uses \Tienvx\Bundle\MbtBundle\Plugin\PluginManager */ -class GeneratorManagerTest extends TestCase +class GeneratorManagerTest extends PluginManagerTest { - protected GeneratorManager $generatorManager; - protected GeneratorInterface $generator; - protected ServiceLocator $locator; + protected array $plugins = ['random']; + protected string $getMethod = 'getGenerator'; - protected function setUp(): void + protected function getPluginManagerClass(): string { - $this->generator = $this->createMock(GeneratorInterface::class); - $this->locator = $this->createMock(ServiceLocator::class); - $plugins = ['random']; - $this->generatorManager = new GeneratorManager($this->locator, $plugins); + return GeneratorManager::class; } - public function testGet() + protected function getPluginInterface(): string { - $this->locator->expects($this->once())->method('has')->with('random')->willReturn(true); - $this->locator->expects($this->once())->method('get')->with('random')->willReturn($this->generator); - $this->assertSame($this->generator, $this->generatorManager->get('random')); + return GeneratorInterface::class; } - public function testAll() + protected function getInvalidPluginExceptionMessage(string $plugin): string { - $this->assertSame(['random'], $this->generatorManager->all()); - } - - public function testHasRandom() - { - $this->locator->expects($this->once())->method('has')->with('random')->willReturn(true); - $this->assertTrue($this->generatorManager->has('random')); - } - - public function testDoesNotHaveOther(): void - { - $this->locator->expects($this->never())->method('has'); - $this->assertFalse($this->generatorManager->has('other')); - $this->expectException(UnexpectedValueException::class); - $this->expectExceptionMessage('Generator "other" does not exist.'); - $this->generatorManager->getGenerator('other'); + return sprintf('Generator "%s" does not exist.', $plugin); } } diff --git a/tests/Plugin/PluginManagerTest.php b/tests/Plugin/PluginManagerTest.php new file mode 100644 index 00000000..dc94e1d8 --- /dev/null +++ b/tests/Plugin/PluginManagerTest.php @@ -0,0 +1,87 @@ +plugin = $this->createMock($this->getPluginInterface()); + $this->locator = $this->createMock(ServiceLocator::class); + $pluginManagerClass = $this->getPluginManagerClass(); + $this->pluginManager = new $pluginManagerClass($this->locator, $this->plugins); + } + + /** + * @dataProvider pluginProvider + */ + public function testGet(string $plugin): void + { + $this->locator->expects($this->once())->method('has')->with($plugin)->willReturn(true); + $this->locator->expects($this->once())->method('get')->with($plugin)->willReturn($this->plugin); + $this->assertSame($this->plugin, $this->pluginManager->{$this->getMethod}($plugin)); + } + + public function testAll(): void + { + $this->assertSame($this->plugins, $this->pluginManager->all()); + } + + /** + * @dataProvider pluginProvider + */ + public function testHas(string $plugin): void + { + $this->locator->expects($this->once())->method('has')->with($plugin)->willReturn(true); + $this->assertTrue($this->pluginManager->has($plugin)); + } + + public function testInvalidPlugin(): void + { + $plugin = 'invalid'; + $this->assertFalse(in_array($plugin, $this->plugins)); + $this->locator->expects($this->never())->method('has'); + $this->assertFalse($this->pluginManager->has($plugin)); + $this->expectExceptionObject(new UnexpectedValueException($this->getInvalidPluginExceptionMessage($plugin))); + $this->pluginManager->get($plugin); + } + + protected function getPluginManagerClass(): string + { + return PluginManager::class; + } + + protected function getPluginInterface(): string + { + return PluginInterface::class; + } + + protected function getInvalidPluginExceptionMessage(string $plugin): string + { + return sprintf('Plugin "%s" does not exist.', $plugin); + } + + public function pluginProvider(): array + { + return [ + $this->plugins, + ]; + } +} diff --git a/tests/Reducer/ReducerManagerTest.php b/tests/Reducer/ReducerManagerTest.php index 54228dcb..ec4c779c 100644 --- a/tests/Reducer/ReducerManagerTest.php +++ b/tests/Reducer/ReducerManagerTest.php @@ -2,60 +2,32 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Reducer; -use PHPUnit\Framework\TestCase; -use Symfony\Component\DependencyInjection\ServiceLocator; -use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; use Tienvx\Bundle\MbtBundle\Reducer\ReducerInterface; use Tienvx\Bundle\MbtBundle\Reducer\ReducerManager; +use Tienvx\Bundle\MbtBundle\Tests\Plugin\PluginManagerTest; /** * @covers \Tienvx\Bundle\MbtBundle\Reducer\ReducerManager - * @covers \Tienvx\Bundle\MbtBundle\Plugin\AbstractPluginManager + * + * @uses \Tienvx\Bundle\MbtBundle\Plugin\PluginManager */ -class ReducerManagerTest extends TestCase +class ReducerManagerTest extends PluginManagerTest { - protected ReducerManager $reducerManager; - protected ReducerInterface $reducer; - protected ServiceLocator $locator; + protected array $plugins = ['split', 'random']; + protected string $getMethod = 'getReducer'; - protected function setUp(): void + protected function getPluginManagerClass(): string { - $this->reducer = $this->createMock(ReducerInterface::class); - $this->locator = $this->createMock(ServiceLocator::class); - $plugins = ['split', 'random']; - $this->reducerManager = new ReducerManager($this->locator, $plugins); + return ReducerManager::class; } - public function testGet(): void + protected function getPluginInterface(): string { - $this->locator->expects($this->once())->method('has')->with('random')->willReturn(true); - $this->locator->expects($this->once())->method('get')->with('random')->willReturn($this->reducer); - $this->assertSame($this->reducer, $this->reducerManager->get('random')); + return ReducerInterface::class; } - public function testAll(): void + protected function getInvalidPluginExceptionMessage(string $plugin): string { - $this->assertSame(['split', 'random'], $this->reducerManager->all()); - } - - public function testHasSplit(): void - { - $this->locator->expects($this->once())->method('has')->with('split')->willReturn(true); - $this->assertTrue($this->reducerManager->has('split')); - } - - public function testHasRandom(): void - { - $this->locator->expects($this->once())->method('has')->with('random')->willReturn(true); - $this->assertTrue($this->reducerManager->has('random')); - } - - public function testDoesNotHaveOther(): void - { - $this->locator->expects($this->never())->method('has'); - $this->assertFalse($this->reducerManager->has('other')); - $this->expectException(UnexpectedValueException::class); - $this->expectExceptionMessage('Reducer "other" does not exist.'); - $this->reducerManager->getReducer('other'); + return sprintf('Reducer "%s" does not exist.', $plugin); } } From 9fe2547ec46a5bd221800b74e9a86ec782795e7b Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 9 Apr 2022 21:25:06 +0700 Subject: [PATCH 11/85] Test message along with message handler --- .../MessageHandler/RecordVideoMessageHandlerTest.php | 8 ++++---- tests/MessageHandler/ReduceBugMessageHandlerTest.php | 8 ++++---- .../MessageHandler/ReduceStepsMessageHandlerTest.php | 8 ++++---- tests/MessageHandler/ReportBugMessageHandlerTest.php | 8 ++++---- tests/MessageHandler/RunTaskMessageHandlerTest.php | 12 +++++------- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/tests/MessageHandler/RecordVideoMessageHandlerTest.php b/tests/MessageHandler/RecordVideoMessageHandlerTest.php index 9b5fc94e..ba1c8a78 100644 --- a/tests/MessageHandler/RecordVideoMessageHandlerTest.php +++ b/tests/MessageHandler/RecordVideoMessageHandlerTest.php @@ -9,24 +9,24 @@ /** * @covers \Tienvx\Bundle\MbtBundle\MessageHandler\RecordVideoMessageHandler - * - * @uses \Tienvx\Bundle\MbtBundle\Message\RecordVideoMessage + * @covers \Tienvx\Bundle\MbtBundle\Message\RecordVideoMessage */ class RecordVideoMessageHandlerTest extends TestCase { protected BugHelperInterface $bugHelper; protected RecordVideoMessageHandler $handler; + protected RecordVideoMessage $message; protected function setUp(): void { $this->bugHelper = $this->createMock(BugHelperInterface::class); $this->handler = new RecordVideoMessageHandler($this->bugHelper); + $this->message = new RecordVideoMessage(123); } public function testInvoke(): void { $this->bugHelper->expects($this->once())->method('recordVideo')->with(123); - $message = new RecordVideoMessage(123); - call_user_func($this->handler, $message); + call_user_func($this->handler, $this->message); } } diff --git a/tests/MessageHandler/ReduceBugMessageHandlerTest.php b/tests/MessageHandler/ReduceBugMessageHandlerTest.php index bf314e54..6d9fe3fa 100644 --- a/tests/MessageHandler/ReduceBugMessageHandlerTest.php +++ b/tests/MessageHandler/ReduceBugMessageHandlerTest.php @@ -9,24 +9,24 @@ /** * @covers \Tienvx\Bundle\MbtBundle\MessageHandler\ReduceBugMessageHandler - * - * @uses \Tienvx\Bundle\MbtBundle\Message\ReduceBugMessage + * @covers \Tienvx\Bundle\MbtBundle\Message\ReduceBugMessage */ class ReduceBugMessageHandlerTest extends TestCase { protected BugHelperInterface $bugHelper; protected ReduceBugMessageHandler $handler; + protected ReduceBugMessage $message; protected function setUp(): void { $this->bugHelper = $this->createMock(BugHelperInterface::class); $this->handler = new ReduceBugMessageHandler($this->bugHelper); + $this->message = new ReduceBugMessage(123); } public function testInvoke(): void { $this->bugHelper->expects($this->once())->method('reduceBug')->with(123); - $message = new ReduceBugMessage(123); - call_user_func($this->handler, $message); + call_user_func($this->handler, $this->message); } } diff --git a/tests/MessageHandler/ReduceStepsMessageHandlerTest.php b/tests/MessageHandler/ReduceStepsMessageHandlerTest.php index 2c161caf..5eec79c6 100644 --- a/tests/MessageHandler/ReduceStepsMessageHandlerTest.php +++ b/tests/MessageHandler/ReduceStepsMessageHandlerTest.php @@ -9,24 +9,24 @@ /** * @covers \Tienvx\Bundle\MbtBundle\MessageHandler\ReduceStepsMessageHandler - * - * @uses \Tienvx\Bundle\MbtBundle\Message\ReduceStepsMessage + * @covers \Tienvx\Bundle\MbtBundle\Message\ReduceStepsMessage */ class ReduceStepsMessageHandlerTest extends TestCase { protected BugHelperInterface $bugHelper; protected ReduceStepsMessageHandler $handler; + protected ReduceStepsMessage $message; protected function setUp(): void { $this->bugHelper = $this->createMock(BugHelperInterface::class); $this->handler = new ReduceStepsMessageHandler($this->bugHelper); + $this->message = new ReduceStepsMessage(123, 6, 1, 2); } public function testInvoke(): void { $this->bugHelper->expects($this->once())->method('reduceSteps')->with(123, 6, 1, 2); - $message = new ReduceStepsMessage(123, 6, 1, 2); - call_user_func($this->handler, $message); + call_user_func($this->handler, $this->message); } } diff --git a/tests/MessageHandler/ReportBugMessageHandlerTest.php b/tests/MessageHandler/ReportBugMessageHandlerTest.php index 7015aff3..a1e89fb3 100644 --- a/tests/MessageHandler/ReportBugMessageHandlerTest.php +++ b/tests/MessageHandler/ReportBugMessageHandlerTest.php @@ -9,24 +9,24 @@ /** * @covers \Tienvx\Bundle\MbtBundle\MessageHandler\ReportBugMessageHandler - * - * @uses \Tienvx\Bundle\MbtBundle\Message\ReportBugMessage + * @covers \Tienvx\Bundle\MbtBundle\Message\ReportBugMessage */ class ReportBugMessageHandlerTest extends TestCase { protected BugHelperInterface $bugHelper; protected ReportBugMessageHandler $handler; + protected ReportBugMessage $message; protected function setUp(): void { $this->bugHelper = $this->createMock(BugHelperInterface::class); $this->handler = new ReportBugMessageHandler($this->bugHelper); + $this->message = new ReportBugMessage(123); } public function testInvoke(): void { $this->bugHelper->expects($this->once())->method('reportBug')->with(123); - $message = new ReportBugMessage(123); - call_user_func($this->handler, $message); + call_user_func($this->handler, $this->message); } } diff --git a/tests/MessageHandler/RunTaskMessageHandlerTest.php b/tests/MessageHandler/RunTaskMessageHandlerTest.php index d1c870aa..d1e05d3d 100644 --- a/tests/MessageHandler/RunTaskMessageHandlerTest.php +++ b/tests/MessageHandler/RunTaskMessageHandlerTest.php @@ -9,26 +9,24 @@ /** * @covers \Tienvx\Bundle\MbtBundle\MessageHandler\RunTaskMessageHandler - * - * @uses \Tienvx\Bundle\MbtBundle\Message\RunTaskMessage + * @covers \Tienvx\Bundle\MbtBundle\Message\RunTaskMessage */ class RunTaskMessageHandlerTest extends TestCase { protected TaskHelperInterface $taskHelper; protected RunTaskMessageHandler $handler; + protected RunTaskMessage $message; protected function setUp(): void { $this->taskHelper = $this->createMock(TaskHelperInterface::class); - $this->handler = new RunTaskMessageHandler( - $this->taskHelper - ); + $this->handler = new RunTaskMessageHandler($this->taskHelper); + $this->message = new RunTaskMessage(123); } public function testInvoke(): void { $this->taskHelper->expects($this->once())->method('run')->with(123); - $message = new RunTaskMessage(123); - call_user_func($this->handler, $message); + call_user_func($this->handler, $this->message); } } From facfc35d9ee60819fa9fc516c31f79e7d9b67295 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 10 Apr 2022 00:27:38 +0700 Subject: [PATCH 12/85] Test validate revision --- src/Entity/Model.php | 6 - src/Model/Model.php | 10 +- tests/Channel/ChannelManagerTest.php | 10 +- tests/Entity/Model/RevisionTest.php | 114 ++++++++-- tests/Entity/ModelTest.php | 273 ++--------------------- tests/Entity/ProgressTest.php | 17 +- tests/Generator/GeneratorManagerTest.php | 10 +- tests/Model/Model/RevisionTest.php | 175 +++++++++++++++ tests/Model/ModelTest.php | 82 +++++++ tests/Model/ProgressTest.php | 9 +- tests/Plugin/PluginManagerTest.php | 13 +- tests/Reducer/ReducerManagerTest.php | 10 +- 12 files changed, 414 insertions(+), 315 deletions(-) create mode 100644 tests/Model/Model/RevisionTest.php create mode 100644 tests/Model/ModelTest.php diff --git a/src/Entity/Model.php b/src/Entity/Model.php index 204004a5..1fe43d83 100644 --- a/src/Entity/Model.php +++ b/src/Entity/Model.php @@ -4,7 +4,6 @@ use DateTime; use DateTimeInterface; -use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; @@ -65,11 +64,6 @@ class Model extends BaseModel */ protected Collection $revisions; - public function __construct() - { - $this->revisions = new ArrayCollection(); - } - /** * @ORM\PrePersist */ diff --git a/src/Model/Model.php b/src/Model/Model.php index 1577b46e..53e15f93 100644 --- a/src/Model/Model.php +++ b/src/Model/Model.php @@ -3,10 +3,11 @@ namespace Tienvx\Bundle\MbtBundle\Model; use DateTimeInterface; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; -abstract class Model implements ModelInterface +class Model implements ModelInterface { protected ?int $id = null; protected ?int $author = null; @@ -17,6 +18,11 @@ abstract class Model implements ModelInterface protected DateTimeInterface $updatedAt; protected DateTimeInterface $createdAt; + public function __construct() + { + $this->revisions = new ArrayCollection(); + } + public function setId(int $id) { $this->id = $id; @@ -58,7 +64,7 @@ public function setTags(?string $tags): void } /** - * @return Collection|RevisionInterface[] + * @return Collection */ public function getRevisions(): Collection { diff --git a/tests/Channel/ChannelManagerTest.php b/tests/Channel/ChannelManagerTest.php index 3d6a5ce4..08e35235 100644 --- a/tests/Channel/ChannelManagerTest.php +++ b/tests/Channel/ChannelManagerTest.php @@ -4,6 +4,8 @@ use Tienvx\Bundle\MbtBundle\Channel\ChannelInterface; use Tienvx\Bundle\MbtBundle\Channel\ChannelManager; +use Tienvx\Bundle\MbtBundle\Plugin\PluginInterface; +use Tienvx\Bundle\MbtBundle\Plugin\PluginManagerInterface; use Tienvx\Bundle\MbtBundle\Tests\Plugin\PluginManagerTest; /** @@ -16,14 +18,14 @@ class ChannelManagerTest extends PluginManagerTest protected array $plugins = ['email', 'slack/chat']; protected string $getMethod = 'getChannel'; - protected function getPluginManagerClass(): string + protected function createPluginManager(): PluginManagerInterface { - return ChannelManager::class; + return new ChannelManager($this->locator, $this->plugins); } - protected function getPluginInterface(): string + protected function createPlugin(): PluginInterface { - return ChannelInterface::class; + return $this->createMock(ChannelInterface::class); } protected function getInvalidPluginExceptionMessage(string $plugin): string diff --git a/tests/Entity/Model/RevisionTest.php b/tests/Entity/Model/RevisionTest.php index ceffe6d3..50c17408 100644 --- a/tests/Entity/Model/RevisionTest.php +++ b/tests/Entity/Model/RevisionTest.php @@ -2,46 +2,112 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Entity\Model; -use PHPUnit\Framework\TestCase; -use Tienvx\Bundle\MbtBundle\Entity\Model; +use Symfony\Component\Validator\Validation; +use Symfony\Component\Validator\Validator\ValidatorInterface; use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; -use Tienvx\Bundle\MbtBundle\Model\ModelInterface; +use Tienvx\Bundle\MbtBundle\Tests\Fixtures\Validator\CustomConstraintValidatorFactory; +use Tienvx\Bundle\MbtBundle\Tests\Model\Model\RevisionTest as RevisionModelTest; /** + * @covers \Tienvx\Bundle\MbtBundle\Entity\Model\Revision + * + * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision * @uses \Tienvx\Bundle\MbtBundle\Entity\Model * @uses \Tienvx\Bundle\MbtBundle\Model\Model - * @covers \Tienvx\Bundle\MbtBundle\Entity\Model\Revision - * @covers \Tienvx\Bundle\MbtBundle\Model\Model\Revision + * @uses \Tienvx\Bundle\MbtBundle\Command\CommandRunner + * @uses \Tienvx\Bundle\MbtBundle\Command\CommandRunnerManager + * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\AlertCommandRunner + * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\AssertionRunner + * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\KeyboardCommandRunner + * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\MouseCommandRunner + * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\ScriptCommandRunner + * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\StoreCommandRunner + * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\WaitCommandRunner + * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\WindowCommandRunner + * @uses \Tienvx\Bundle\MbtBundle\Validator\ValidCommandValidator + * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision\Command + * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision\Place + * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision\Transition + * @uses \Tienvx\Bundle\MbtBundle\Validator\TagsValidator + * @uses \Tienvx\Bundle\MbtBundle\Validator\ValidCommand + * @uses \Tienvx\Bundle\MbtBundle\ValueObject\Model\Command */ -class RevisionTest extends TestCase +class RevisionTest extends RevisionModelTest { - protected RevisionInterface $revision; - protected RevisionInterface $activeRevision; - protected ModelInterface $model; + protected ValidatorInterface $validator; protected function setUp(): void { - $this->revision = new Revision(); - $this->revision->setId(1); - $this->activeRevision = new Revision(); - $this->activeRevision->setId(2); - $this->model = new Model(); - $this->model->setLabel('Model label'); + parent::setUp(); + $this->validator = Validation::createValidatorBuilder() + ->enableAnnotationMapping() + ->setConstraintValidatorFactory(new CustomConstraintValidatorFactory()) + ->getValidator(); + } + + public function testValidateInvalidRevision(): void + { + $violations = $this->validator->validate($this->revision); + $this->assertCount(11, $violations); + $message = 'Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).transitions[0].toPlaces: + mbt.model.places_invalid +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).transitions[1].fromPlaces: + mbt.model.places_invalid +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).transitions: + mbt.model.missing_start_transition +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).places[0].label: + This value should not be blank. (code c1051bb4-d103-4f74-8988-acbcafc7fdc3) +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).places[0].commands[0].command: + mbt.model.command.invalid_command (code ba5fd751-cbdf-45ab-a1e7-37045d5ef44b) +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).places[0].commands[0].command: + This value should not be blank. (code c1051bb4-d103-4f74-8988-acbcafc7fdc3) +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).places[0].commands[1].target: + mbt.model.command.required_target (code ba5fd751-cbdf-45ab-a1e7-37045d5ef44b) +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).places[1].commands[0].command: + mbt.model.command.invalid_command (code ba5fd751-cbdf-45ab-a1e7-37045d5ef44b) +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).places[1].commands[1].value: + mbt.model.command.required_value (code ba5fd751-cbdf-45ab-a1e7-37045d5ef44b) +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).transitions[1].label: + This value should not be blank. (code c1051bb4-d103-4f74-8988-acbcafc7fdc3) +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).transitions[1].toPlaces: + mbt.model.missing_to_places (code bef8e338-6ae5-4caf-b8e2-50e7b0579e69) +'; + $this->assertSame($message, (string) $violations); } - public function testConvertToString(): void + public function testValidateInvalidRevisionTooManyStartTransitions(): void { - $this->assertSame('', (string) $this->revision); - $this->model->setActiveRevision($this->revision); - $this->assertSame($this->model->getLabel(), (string) $this->revision); + $this->revision->getTransition(0)->setFromPlaces([]); + $this->revision->getTransition(1)->setFromPlaces([]); + $violations = $this->validator->validate($this->revision); + $this->assertCount(10, $violations); + $message = 'Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).transitions[0].toPlaces: + mbt.model.places_invalid +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).transitions: + mbt.model.too_many_start_transitions +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).places[0].label: + This value should not be blank. (code c1051bb4-d103-4f74-8988-acbcafc7fdc3) +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).places[0].commands[0].command: + mbt.model.command.invalid_command (code ba5fd751-cbdf-45ab-a1e7-37045d5ef44b) +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).places[0].commands[0].command: + This value should not be blank. (code c1051bb4-d103-4f74-8988-acbcafc7fdc3) +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).places[0].commands[1].target: + mbt.model.command.required_target (code ba5fd751-cbdf-45ab-a1e7-37045d5ef44b) +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).places[1].commands[0].command: + mbt.model.command.invalid_command (code ba5fd751-cbdf-45ab-a1e7-37045d5ef44b) +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).places[1].commands[1].value: + mbt.model.command.required_value (code ba5fd751-cbdf-45ab-a1e7-37045d5ef44b) +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).transitions[1].label: + This value should not be blank. (code c1051bb4-d103-4f74-8988-acbcafc7fdc3) +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).transitions[1].toPlaces: + mbt.model.missing_to_places (code bef8e338-6ae5-4caf-b8e2-50e7b0579e69) +'; + $this->assertSame($message, (string) $violations); } - public function testIsLatest(): void + protected function createRevision(): RevisionInterface { - $this->model->setActiveRevision($this->revision); - $this->model->setActiveRevision($this->activeRevision); - $this->assertFalse($this->revision->isLatest()); - $this->assertTrue($this->activeRevision->isLatest()); + return new Revision(); } } diff --git a/tests/Entity/ModelTest.php b/tests/Entity/ModelTest.php index 2da76159..b74bcdc2 100644 --- a/tests/Entity/ModelTest.php +++ b/tests/Entity/ModelTest.php @@ -2,279 +2,40 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Entity; -use PHPUnit\Framework\TestCase; -use Symfony\Component\Validator\Validation; -use Symfony\Component\Validator\Validator\ValidatorInterface; use Tienvx\Bundle\MbtBundle\Entity\Model; -use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; -use Tienvx\Bundle\MbtBundle\Tests\Fixtures\Validator\CustomConstraintValidatorFactory; -use Tienvx\Bundle\MbtBundle\ValueObject\Model\Command; -use Tienvx\Bundle\MbtBundle\ValueObject\Model\Place; -use Tienvx\Bundle\MbtBundle\ValueObject\Model\Transition; +use Tienvx\Bundle\MbtBundle\Model\ModelInterface; +use Tienvx\Bundle\MbtBundle\Tests\Model\ModelTest as ModelModelTest; /** * @covers \Tienvx\Bundle\MbtBundle\Entity\Model - * @covers \Tienvx\Bundle\MbtBundle\Model\Model * - * @uses \Tienvx\Bundle\MbtBundle\Command\CommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\CommandRunnerManager - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\AlertCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\AssertionRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\KeyboardCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\MouseCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\ScriptCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\StoreCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\WaitCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\WindowCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Validator\ValidCommandValidator + * @uses \Tienvx\Bundle\MbtBundle\Model\Model * @uses \Tienvx\Bundle\MbtBundle\Entity\Model\Revision * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision - * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision\Command - * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision\Place - * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision\Transition - * @uses \Tienvx\Bundle\MbtBundle\Validator\TagsValidator - * @uses \Tienvx\Bundle\MbtBundle\Validator\ValidCommand - * @uses \Tienvx\Bundle\MbtBundle\ValueObject\Model\Command */ -class ModelTest extends TestCase +class ModelTest extends ModelModelTest { - protected Model $model; - protected Revision $revision; - protected ValidatorInterface $validator; - - protected function setUp(): void - { - $this->revision = new Revision(); - $this->revision->setId(1); - $places = [ - $p1 = new Place(), - $p2 = new Place(), - ]; - $p1->setLabel(''); - $p1->setCommands([ - $c1 = new Command(), - $c2 = new Command(), - ]); - $c1->setCommand(''); - $c1->setTarget('css=.name'); - $c1->setValue('test'); - $c2->setCommand('click'); - $c2->setTarget(null); - $c2->setValue('test'); - $p2->setLabel('p2'); - $p2->setCommands([ - $c3 = new Command(), - $c4 = new Command(), - ]); - $c3->setCommand('doNoThing'); - $c3->setTarget('css=.about'); - $c3->setValue('test'); - $c4->setCommand('clickAt'); - $c4->setTarget('css=.avatar'); - $c4->setValue(null); - $this->revision->setPlaces($places); - $transitions = [ - $t1 = new Transition(), - $t2 = new Transition(), - ]; - $t1->setLabel('t1'); - $t1->setFromPlaces([1]); - $t1->setToPlaces([1, 2]); - $t2->setLabel(''); - $t2->setFromPlaces([1, 2]); - $t2->setToPlaces([]); - $t2->setGuard('count > 1'); - $this->revision->setTransitions($transitions); - - $this->model = new Model(); - $this->model->setLabel(''); - $this->model->setTags('tag1,tag1,tag2,,tag3'); - $this->model->setActiveRevision($this->revision); - - $this->validator = Validation::createValidatorBuilder() - ->enableAnnotationMapping() - ->setConstraintValidatorFactory(new CustomConstraintValidatorFactory()) - ->getValidator(); - } - public function testPrePersist(): void { - $model = new Model(); - $model->prePersist(); - $this->assertInstanceOf(\DateTime::class, $model->getCreatedAt()); - $this->assertInstanceOf(\DateTime::class, $model->getUpdatedAt()); + $this->model->prePersist(); + $this->assertInstanceOf(\DateTime::class, $this->model->getCreatedAt()); + $this->assertInstanceOf(\DateTime::class, $this->model->getUpdatedAt()); } public function testPreUpdate(): void { - $model = new Model(); - $model->prePersist(); - $model->preUpdate(); - $this->assertInstanceOf(\DateTime::class, $updatedAt = $model->getUpdatedAt()); - $model->preUpdate(); - $this->assertTrue($model->getUpdatedAt() instanceof \DateTime && $updatedAt !== $model->getUpdatedAt()); - } - - public function testActiveRevision(): void - { - $model = new Model(); - $model->setId(1); - $model->setActiveRevision($this->revision); - $this->assertSame($this->revision->getId(), $model->getActiveRevision()->getId()); - $this->assertSame($model, $this->revision->getModel()); - $this->assertSame($model->getId(), $this->revision->getModel()->getId()); - $revision = new Revision(); - $revision->setId(2); - $model->setActiveRevision($revision); - $this->assertNotSame($this->revision->getId(), $model->getActiveRevision()->getId()); - $this->assertSame($model, $revision->getModel()); - $this->assertSame($model->getId(), $revision->getModel()->getId()); - } - - public function testAddRemoveRevision(): void - { - $this->assertTrue($this->model->getRevisions()->contains($this->revision)); - $this->model->removeRevision($this->revision); - $this->assertFalse($this->model->getRevisions()->contains($this->revision)); - $this->assertCount(0, $this->model->getRevisions()); - $revision = new Revision(); - $this->model->addRevision($revision); - $this->assertTrue($this->model->getRevisions()->contains($revision)); - $this->assertCount(1, $this->model->getRevisions()); - } - - public function testValidateInvalidModel(): void - { - $violations = $this->validator->validate($this->model); - $this->assertCount(13, $violations); - $message = 'Object(Tienvx\Bundle\MbtBundle\Entity\Model).label: - This value should not be blank. (code c1051bb4-d103-4f74-8988-acbcafc7fdc3) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).tags: - The tags should be unique and not blank. (code 628fca96-35f8-11eb-adc1-0242ac120002) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.transitions[0].toPlaces: - mbt.model.places_invalid -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.transitions[1].fromPlaces: - mbt.model.places_invalid -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.transitions: - mbt.model.missing_start_transition -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.places[0].label: - This value should not be blank. (code c1051bb4-d103-4f74-8988-acbcafc7fdc3) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.places[0].commands[0].command: - mbt.model.command.invalid_command (code ba5fd751-cbdf-45ab-a1e7-37045d5ef44b) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.places[0].commands[0].command: - This value should not be blank. (code c1051bb4-d103-4f74-8988-acbcafc7fdc3) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.places[0].commands[1].target: - mbt.model.command.required_target (code ba5fd751-cbdf-45ab-a1e7-37045d5ef44b) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.places[1].commands[0].command: - mbt.model.command.invalid_command (code ba5fd751-cbdf-45ab-a1e7-37045d5ef44b) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.places[1].commands[1].value: - mbt.model.command.required_value (code ba5fd751-cbdf-45ab-a1e7-37045d5ef44b) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.transitions[1].label: - This value should not be blank. (code c1051bb4-d103-4f74-8988-acbcafc7fdc3) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.transitions[1].toPlaces: - mbt.model.missing_to_places (code bef8e338-6ae5-4caf-b8e2-50e7b0579e69) -'; - $this->assertSame($message, (string) $violations); - } - - public function testValidateInvalidModelTooManyStartTransitions(): void - { - $this->model->getActiveRevision()->getTransition(0)->setFromPlaces([]); - $this->model->getActiveRevision()->getTransition(1)->setFromPlaces([]); - $violations = $this->validator->validate($this->model); - $this->assertCount(12, $violations); - $message = 'Object(Tienvx\Bundle\MbtBundle\Entity\Model).label: - This value should not be blank. (code c1051bb4-d103-4f74-8988-acbcafc7fdc3) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).tags: - The tags should be unique and not blank. (code 628fca96-35f8-11eb-adc1-0242ac120002) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.transitions[0].toPlaces: - mbt.model.places_invalid -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.transitions: - mbt.model.too_many_start_transitions -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.places[0].label: - This value should not be blank. (code c1051bb4-d103-4f74-8988-acbcafc7fdc3) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.places[0].commands[0].command: - mbt.model.command.invalid_command (code ba5fd751-cbdf-45ab-a1e7-37045d5ef44b) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.places[0].commands[0].command: - This value should not be blank. (code c1051bb4-d103-4f74-8988-acbcafc7fdc3) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.places[0].commands[1].target: - mbt.model.command.required_target (code ba5fd751-cbdf-45ab-a1e7-37045d5ef44b) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.places[1].commands[0].command: - mbt.model.command.invalid_command (code ba5fd751-cbdf-45ab-a1e7-37045d5ef44b) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.places[1].commands[1].value: - mbt.model.command.required_value (code ba5fd751-cbdf-45ab-a1e7-37045d5ef44b) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.transitions[1].label: - This value should not be blank. (code c1051bb4-d103-4f74-8988-acbcafc7fdc3) -Object(Tienvx\Bundle\MbtBundle\Entity\Model).activeRevision.transitions[1].toPlaces: - mbt.model.missing_to_places (code bef8e338-6ae5-4caf-b8e2-50e7b0579e69) -'; - $this->assertSame($message, (string) $violations); + $this->model->prePersist(); + $this->model->preUpdate(); + $this->assertInstanceOf(\DateTime::class, $updatedAt = $this->model->getUpdatedAt()); + $this->model->preUpdate(); + $this->assertTrue( + $this->model->getUpdatedAt() instanceof \DateTime + && $updatedAt !== $this->model->getUpdatedAt() + ); } - public function testToArray(): void + protected function createModel(): ModelInterface { - $this->assertSame([ - 'label' => '', - 'tags' => 'tag1,tag1,tag2,,tag3', - 'places' => [ - 0 => [ - 'label' => '', - 'commands' => [ - 0 => [ - 'command' => '', - 'target' => 'css=.name', - 'value' => 'test', - ], - 1 => [ - 'command' => 'click', - 'target' => null, - 'value' => 'test', - ], - ], - ], - 1 => [ - 'label' => 'p2', - 'commands' => [ - 0 => [ - 'command' => 'doNoThing', - 'target' => 'css=.about', - 'value' => 'test', - ], - 1 => [ - 'command' => 'clickAt', - 'target' => 'css=.avatar', - 'value' => null, - ], - ], - ], - ], - 'transitions' => [ - 0 => [ - 'label' => 't1', - 'guard' => null, - 'fromPlaces' => [ - 0 => 1, - ], - 'toPlaces' => [ - 0 => 1, - 1 => 2, - ], - 'commands' => [ - ], - ], - 1 => [ - 'label' => '', - 'guard' => 'count > 1', - 'fromPlaces' => [ - 0 => 1, - 1 => 2, - ], - 'toPlaces' => [ - ], - 'commands' => [ - ], - ], - ], - ], $this->model->toArray()); + return new Model(); } } diff --git a/tests/Entity/ProgressTest.php b/tests/Entity/ProgressTest.php index 5c5c54fe..33952b94 100644 --- a/tests/Entity/ProgressTest.php +++ b/tests/Entity/ProgressTest.php @@ -2,28 +2,33 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Entity; -use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Validation; use Tienvx\Bundle\MbtBundle\Entity\Progress; +use Tienvx\Bundle\MbtBundle\Model\ProgressInterface; +use Tienvx\Bundle\MbtBundle\Tests\Model\ProgressTest as ProgressModelTest; /** * @covers \Tienvx\Bundle\MbtBundle\Entity\Progress * @covers \Tienvx\Bundle\MbtBundle\Model\Progress */ -class ProgressTest extends TestCase +class ProgressTest extends ProgressModelTest { public function testValidateInvalidProgress(): void { - $progress = new Progress(); - $progress->setTotal(10); - $progress->setProcessed(11); + $this->progress->setTotal(10); + $this->progress->setProcessed(11); $validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator(); - $violations = $validator->validate($progress); + $violations = $validator->validate($this->progress); $this->assertCount(1, $violations); $message = 'Object(Tienvx\Bundle\MbtBundle\Entity\Progress).processed: Processed should be less than or equal to total. '; $this->assertSame($message, (string) $violations); } + + protected function createProgress(): ProgressInterface + { + return new Progress(); + } } diff --git a/tests/Generator/GeneratorManagerTest.php b/tests/Generator/GeneratorManagerTest.php index ec1268cf..999b8d6f 100644 --- a/tests/Generator/GeneratorManagerTest.php +++ b/tests/Generator/GeneratorManagerTest.php @@ -4,6 +4,8 @@ use Tienvx\Bundle\MbtBundle\Generator\GeneratorInterface; use Tienvx\Bundle\MbtBundle\Generator\GeneratorManager; +use Tienvx\Bundle\MbtBundle\Plugin\PluginInterface; +use Tienvx\Bundle\MbtBundle\Plugin\PluginManagerInterface; use Tienvx\Bundle\MbtBundle\Tests\Plugin\PluginManagerTest; /** @@ -16,14 +18,14 @@ class GeneratorManagerTest extends PluginManagerTest protected array $plugins = ['random']; protected string $getMethod = 'getGenerator'; - protected function getPluginManagerClass(): string + protected function createPluginManager(): PluginManagerInterface { - return GeneratorManager::class; + return new GeneratorManager($this->locator, $this->plugins); } - protected function getPluginInterface(): string + protected function createPlugin(): PluginInterface { - return GeneratorInterface::class; + return $this->createMock(GeneratorInterface::class); } protected function getInvalidPluginExceptionMessage(string $plugin): string diff --git a/tests/Model/Model/RevisionTest.php b/tests/Model/Model/RevisionTest.php new file mode 100644 index 00000000..7bb3d98f --- /dev/null +++ b/tests/Model/Model/RevisionTest.php @@ -0,0 +1,175 @@ +revision = $this->createRevision(); + $this->revision->setId(1); + $this->places = [ + $p1 = new Place(), + $p2 = new Place(), + ]; + $p1->setLabel(''); + $p1->setCommands([ + $c1 = new Command(), + $c2 = new Command(), + ]); + $c1->setCommand(''); + $c1->setTarget('css=.name'); + $c1->setValue('test'); + $c2->setCommand('click'); + $c2->setTarget(null); + $c2->setValue('test'); + $p2->setLabel('p2'); + $p2->setCommands([ + $c3 = new Command(), + $c4 = new Command(), + ]); + $c3->setCommand('doNoThing'); + $c3->setTarget('css=.about'); + $c3->setValue('test'); + $c4->setCommand('clickAt'); + $c4->setTarget('css=.avatar'); + $c4->setValue(null); + $this->revision->setPlaces($this->places); + $this->transitions = [ + $t1 = new Transition(), + $t2 = new Transition(), + ]; + $t1->setLabel('t1'); + $t1->setFromPlaces([1]); + $t1->setToPlaces([1, 2]); + $t2->setLabel(''); + $t2->setFromPlaces([1, 2]); + $t2->setToPlaces([]); + $t2->setGuard('count > 1'); + $this->revision->setTransitions($this->transitions); + $this->model = new Model(); + $this->model->setActiveRevision(new Revision()); + $this->revision->setModel($this->model); + } + + public function testProperties(): void + { + $this->assertSame(1, $this->revision->getId()); + $this->assertSame($this->model, $this->revision->getModel()); + $this->assertSame($this->places, $this->revision->getPlaces()); + $this->assertSame($this->transitions, $this->revision->getTransitions()); + $this->assertSame($this->transitions[0], $this->revision->getTransition(0)); + } + + public function testConvertToString(): void + { + $this->assertSame('', (string) $this->revision); + $this->model->setLabel('model label'); + $this->assertSame('model label', (string) $this->revision); + } + + public function testIsLatest(): void + { + $this->assertFalse($this->revision->isLatest()); + $this->model->setActiveRevision($this->revision); + $this->assertTrue($this->revision->isLatest()); + } + + public function testToArray(): void + { + $this->assertSame([ + 'places' => [ + 0 => [ + 'label' => '', + 'commands' => [ + 0 => [ + 'command' => '', + 'target' => 'css=.name', + 'value' => 'test', + ], + 1 => [ + 'command' => 'click', + 'target' => null, + 'value' => 'test', + ], + ], + ], + 1 => [ + 'label' => 'p2', + 'commands' => [ + 0 => [ + 'command' => 'doNoThing', + 'target' => 'css=.about', + 'value' => 'test', + ], + 1 => [ + 'command' => 'clickAt', + 'target' => 'css=.avatar', + 'value' => null, + ], + ], + ], + ], + 'transitions' => [ + 0 => [ + 'label' => 't1', + 'guard' => null, + 'fromPlaces' => [ + 0 => 1, + ], + 'toPlaces' => [ + 0 => 1, + 1 => 2, + ], + 'commands' => [ + ], + ], + 1 => [ + 'label' => '', + 'guard' => 'count > 1', + 'fromPlaces' => [ + 0 => 1, + 1 => 2, + ], + 'toPlaces' => [ + ], + 'commands' => [ + ], + ], + ], + ], $this->revision->toArray()); + } + + protected function createRevision(): RevisionInterface + { + return new Revision(); + } +} diff --git a/tests/Model/ModelTest.php b/tests/Model/ModelTest.php new file mode 100644 index 00000000..5d535552 --- /dev/null +++ b/tests/Model/ModelTest.php @@ -0,0 +1,82 @@ +revision = new Revision(); + $this->revision->setId(1); + $this->model = $this->createModel(); + $this->model->setId(1); + $this->model->setLabel(''); + $this->model->setTags('tag1,tag1,tag2,,tag3'); + $this->model->setAuthor(12); + $this->model->setActiveRevision($this->revision); + } + + public function testProperties(): void + { + $this->assertSame(1, $this->model->getId()); + $this->assertSame('', $this->model->getLabel()); + $this->assertSame('tag1,tag1,tag2,,tag3', $this->model->getTags()); + $this->assertSame(12, $this->model->getAuthor()); + } + + public function testActiveRevision(): void + { + $this->assertSame($this->revision, $this->model->getActiveRevision()); + $revision = new Revision(); + $revision->setId(2); + $this->model->setActiveRevision($revision); + $this->assertSame($revision, $this->model->getActiveRevision()); + } + + public function testAddRemoveRevision(): void + { + $this->assertTrue($this->model->getRevisions()->contains($this->revision)); + $this->assertCount(1, $this->model->getRevisions()); + $revision = new Revision(); + $this->model->addRevision($revision); + $this->assertTrue($this->model->getRevisions()->contains($revision)); + $this->assertCount(2, $this->model->getRevisions()); + $this->model->removeRevision($this->revision); + $this->assertFalse($this->model->getRevisions()->contains($this->revision)); + $this->assertCount(1, $this->model->getRevisions()); + $this->model->removeRevision($revision); + $this->assertFalse($this->model->getRevisions()->contains($revision)); + $this->assertCount(0, $this->model->getRevisions()); + } + + public function testToArray(): void + { + $this->assertSame([ + 'label' => '', + 'tags' => 'tag1,tag1,tag2,,tag3', + 'places' => [], + 'transitions' => [], + ], $this->model->toArray()); + } + + protected function createModel(): ModelInterface + { + return new Model(); + } +} diff --git a/tests/Model/ProgressTest.php b/tests/Model/ProgressTest.php index 41e38013..600dffca 100644 --- a/tests/Model/ProgressTest.php +++ b/tests/Model/ProgressTest.php @@ -3,7 +3,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Model; use PHPUnit\Framework\TestCase; -use Tienvx\Bundle\MbtBundle\Entity\Progress; +use Tienvx\Bundle\MbtBundle\Model\Progress; use Tienvx\Bundle\MbtBundle\Model\ProgressInterface; /** @@ -15,7 +15,7 @@ class ProgressTest extends TestCase protected function setUp(): void { - $this->progress = new Progress(); + $this->progress = $this->createProgress(); $this->progress->setTotal(10); $this->progress->setProcessed(5); } @@ -40,4 +40,9 @@ public function testSetTotal(): void $this->assertSame(5, $this->progress->getProcessed()); $this->assertSame(15, $this->progress->getTotal()); } + + protected function createProgress(): ProgressInterface + { + return new Progress(); + } } diff --git a/tests/Plugin/PluginManagerTest.php b/tests/Plugin/PluginManagerTest.php index dc94e1d8..0803fa83 100644 --- a/tests/Plugin/PluginManagerTest.php +++ b/tests/Plugin/PluginManagerTest.php @@ -23,10 +23,9 @@ class PluginManagerTest extends TestCase protected function setUp(): void { - $this->plugin = $this->createMock($this->getPluginInterface()); + $this->plugin = $this->createPlugin(); $this->locator = $this->createMock(ServiceLocator::class); - $pluginManagerClass = $this->getPluginManagerClass(); - $this->pluginManager = new $pluginManagerClass($this->locator, $this->plugins); + $this->pluginManager = $this->createPluginManager(); } /** @@ -63,14 +62,14 @@ public function testInvalidPlugin(): void $this->pluginManager->get($plugin); } - protected function getPluginManagerClass(): string + protected function createPluginManager(): PluginManagerInterface { - return PluginManager::class; + return new PluginManager($this->locator, $this->plugins); } - protected function getPluginInterface(): string + protected function createPlugin(): PluginInterface { - return PluginInterface::class; + return $this->createMock(PluginInterface::class); } protected function getInvalidPluginExceptionMessage(string $plugin): string diff --git a/tests/Reducer/ReducerManagerTest.php b/tests/Reducer/ReducerManagerTest.php index ec4c779c..3936ebeb 100644 --- a/tests/Reducer/ReducerManagerTest.php +++ b/tests/Reducer/ReducerManagerTest.php @@ -2,6 +2,8 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Reducer; +use Tienvx\Bundle\MbtBundle\Plugin\PluginInterface; +use Tienvx\Bundle\MbtBundle\Plugin\PluginManagerInterface; use Tienvx\Bundle\MbtBundle\Reducer\ReducerInterface; use Tienvx\Bundle\MbtBundle\Reducer\ReducerManager; use Tienvx\Bundle\MbtBundle\Tests\Plugin\PluginManagerTest; @@ -16,14 +18,14 @@ class ReducerManagerTest extends PluginManagerTest protected array $plugins = ['split', 'random']; protected string $getMethod = 'getReducer'; - protected function getPluginManagerClass(): string + protected function createPluginManager(): PluginManagerInterface { - return ReducerManager::class; + return new ReducerManager($this->locator, $this->plugins); } - protected function getPluginInterface(): string + protected function createPlugin(): PluginInterface { - return ReducerInterface::class; + return $this->createMock(ReducerInterface::class); } protected function getInvalidPluginExceptionMessage(string $plugin): string From fd6bdb11b424944809830d2ea13bbe07f244e575 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 10 Apr 2022 00:33:26 +0700 Subject: [PATCH 13/85] Test set place label --- tests/Model/Model/Revision/PlaceTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Model/Model/Revision/PlaceTest.php b/tests/Model/Model/Revision/PlaceTest.php index be8e22d9..954702ce 100644 --- a/tests/Model/Model/Revision/PlaceTest.php +++ b/tests/Model/Model/Revision/PlaceTest.php @@ -25,6 +25,7 @@ protected function setUp(): void { $this->setUpCommands(); $this->place = new Place(); + $this->place->setLabel('place label'); $this->place->setCommands([ $this->command1, $this->command2, @@ -46,7 +47,7 @@ protected function setUpCommands(): void public function testSerialize(): void { // phpcs:ignore Generic.Files.LineLength - $this->assertSame('O:50:"Tienvx\Bundle\MbtBundle\Model\Model\Revision\Place":2:{s:5:"label";s:0:"";s:8:"commands";a:2:{i:0;a:3:{s:7:"command";s:10:"assertText";s:6:"target";s:10:"css=.title";s:5:"value";s:5:"Hello";}i:1;a:3:{s:7:"command";s:11:"assertAlert";s:6:"target";s:12:"css=.warning";s:5:"value";s:13:"Are you sure?";}}}', serialize($this->place)); + $this->assertSame('O:50:"Tienvx\Bundle\MbtBundle\Model\Model\Revision\Place":2:{s:5:"label";s:11:"place label";s:8:"commands";a:2:{i:0;a:3:{s:7:"command";s:10:"assertText";s:6:"target";s:10:"css=.title";s:5:"value";s:5:"Hello";}i:1;a:3:{s:7:"command";s:11:"assertAlert";s:6:"target";s:12:"css=.warning";s:5:"value";s:13:"Are you sure?";}}}', serialize($this->place)); } public function testUnerialize(): void From 82ab298c95f9ce149c8dde530f273fa193f3f511 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 10 Apr 2022 00:34:57 +0700 Subject: [PATCH 14/85] Test set transition label --- tests/Model/Model/Revision/TransitionTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Model/Model/Revision/TransitionTest.php b/tests/Model/Model/Revision/TransitionTest.php index 60d3f92b..ab82c66e 100644 --- a/tests/Model/Model/Revision/TransitionTest.php +++ b/tests/Model/Model/Revision/TransitionTest.php @@ -27,6 +27,7 @@ protected function setUp(): void { $this->setUpCommands(); $this->transition = new Transition(); + $this->transition->setLabel('transition label'); $this->transition->setGuard('count > 2'); $this->transition->setFromPlaces([1, 2, 3]); $this->transition->setToPlaces([12, 23]); @@ -51,7 +52,7 @@ protected function setUpCommands(): void public function testSerialize(): void { // phpcs:ignore Generic.Files.LineLength - $this->assertSame('O:55:"Tienvx\Bundle\MbtBundle\Model\Model\Revision\Transition":5:{s:5:"label";s:0:"";s:5:"guard";s:9:"count > 2";s:10:"fromPlaces";a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}s:8:"toPlaces";a:2:{i:0;i:12;i:1;i:23;}s:8:"commands";a:2:{i:0;a:3:{s:7:"command";s:4:"type";s:6:"target";s:10:"css=.email";s:5:"value";s:16:"test@example.com";}i:1;a:3:{s:7:"command";s:5:"click";s:6:"target";s:9:"css=.link";s:5:"value";N;}}}', serialize($this->transition)); + $this->assertSame('O:55:"Tienvx\Bundle\MbtBundle\Model\Model\Revision\Transition":5:{s:5:"label";s:16:"transition label";s:5:"guard";s:9:"count > 2";s:10:"fromPlaces";a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}s:8:"toPlaces";a:2:{i:0;i:12;i:1;i:23;}s:8:"commands";a:2:{i:0;a:3:{s:7:"command";s:4:"type";s:6:"target";s:10:"css=.email";s:5:"value";s:16:"test@example.com";}i:1;a:3:{s:7:"command";s:5:"click";s:6:"target";s:9:"css=.link";s:5:"value";N;}}}', serialize($this->transition)); } public function testUnerialize(): void From 2eaada84ed54c3eb7b28210c3e4d1013c7a249ad Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 10 Apr 2022 00:38:56 +0700 Subject: [PATCH 15/85] Test get single place in model's revision --- tests/Model/Model/RevisionTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Model/Model/RevisionTest.php b/tests/Model/Model/RevisionTest.php index 7bb3d98f..93994025 100644 --- a/tests/Model/Model/RevisionTest.php +++ b/tests/Model/Model/RevisionTest.php @@ -85,6 +85,7 @@ public function testProperties(): void $this->assertSame(1, $this->revision->getId()); $this->assertSame($this->model, $this->revision->getModel()); $this->assertSame($this->places, $this->revision->getPlaces()); + $this->assertSame($this->places[0], $this->revision->getPlace(0)); $this->assertSame($this->transitions, $this->revision->getTransitions()); $this->assertSame($this->transitions[0], $this->revision->getTransition(0)); } From adf5e45a4aec15faca84be388b57794b7c007466 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 10 Apr 2022 00:44:13 +0700 Subject: [PATCH 16/85] Test AbstractChannel --- tests/Channel/AbstractChannelTest.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/Channel/AbstractChannelTest.php diff --git a/tests/Channel/AbstractChannelTest.php b/tests/Channel/AbstractChannelTest.php new file mode 100644 index 00000000..3ca0cf87 --- /dev/null +++ b/tests/Channel/AbstractChannelTest.php @@ -0,0 +1,18 @@ +assertSame(ChannelManager::class, AbstractChannel::getManager()); + } +} From d9c5fc459db6b597d3bd79070483eeccbcea8d01 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 10 Apr 2022 10:51:18 +0700 Subject: [PATCH 17/85] Test Step value object --- tests/Model/Bug/StepTest.php | 51 +++++++++++++++++++++++++++--- tests/ValueObject/Bug/StepTest.php | 20 ++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 tests/ValueObject/Bug/StepTest.php diff --git a/tests/Model/Bug/StepTest.php b/tests/Model/Bug/StepTest.php index dedaee59..d9fb84c8 100644 --- a/tests/Model/Bug/StepTest.php +++ b/tests/Model/Bug/StepTest.php @@ -4,9 +4,10 @@ use PHPUnit\Framework\TestCase; use SingleColorPetrinet\Model\Color; +use SingleColorPetrinet\Model\ColorInterface; +use Tienvx\Bundle\MbtBundle\Model\Bug\Step; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; -use Tienvx\Bundle\MbtBundle\ValueObject\Bug\Step; /** * @covers \Tienvx\Bundle\MbtBundle\Model\Bug\Step @@ -16,25 +17,67 @@ class StepTest extends TestCase protected StepInterface $step; protected CommandInterface $command1; protected CommandInterface $command2; + protected array $places = [0 => 1, 1 => 2]; + protected ColorInterface $color; + protected int $transition = 123; protected function setUp(): void { - $this->step = new Step([1, 2], new Color(['key' => 'value']), 123); + $this->color = new Color(['key' => 'value']); + $this->step = $this->createStep(); } public function testSerialize(): void { + $className = get_class($this->step); // phpcs:ignore Generic.Files.LineLength - $this->assertSame('O:44:"Tienvx\Bundle\MbtBundle\ValueObject\Bug\Step":3:{s:5:"color";a:1:{s:3:"key";s:5:"value";}s:6:"places";a:2:{i:0;i:1;i:1;i:2;}s:10:"transition";i:123;}', serialize($this->step)); + $this->assertSame('O:' . strlen($className) . ':"' . $className . '":3:{s:5:"color";a:1:{s:3:"key";s:5:"value";}s:6:"places";a:2:{i:0;i:1;i:1;i:2;}s:10:"transition";i:123;}', serialize($this->step)); } public function testUnerialize(): void { + $className = get_class($this->step); // phpcs:ignore Generic.Files.LineLength - $step = unserialize('O:44:"Tienvx\Bundle\MbtBundle\ValueObject\Bug\Step":3:{s:5:"color";a:1:{s:4:"key1";s:6:"value2";}s:6:"places";a:2:{i:0;i:3;i:1;i:4;}s:10:"transition";i:234;}'); + $step = unserialize('O:' . strlen($className) . ':"' . $className . '":3:{s:5:"color";a:1:{s:4:"key1";s:6:"value2";}s:6:"places";a:2:{i:0;i:3;i:1;i:4;}s:10:"transition";i:234;}'); $this->assertInstanceOf(StepInterface::class, $step); $this->assertSame(['key1' => 'value2'], $step->getColor()->getValues()); $this->assertSame([3, 4], $step->getPlaces()); $this->assertSame(234, $step->getTransition()); } + + public function testClone(): void + { + $step = clone $this->step; + $this->assertNotSame($this->step->getColor(), $step->getColor()); + $this->assertSame($this->step->getColor()->getValues(), $step->getColor()->getValues()); + } + + /** + * @dataProvider nodeIdProvider + */ + public function testGetUniqueNodeId(?array $places, ?ColorInterface $color, string $id): void + { + if ($places) { + $this->step->setPlaces($places); + } + if ($color) { + $this->step->setColor($color); + } + $this->assertSame($id, $this->step->getUniqueNodeId()); + } + + public function nodeIdProvider(): array + { + return [ + [null, null, 'f179bfa0d0b5b6751e353f049461eda8'], + [null, new Color(['key1' => 'value1']), 'e13d72c92c38781375d3a400df07d43a'], + [[0 => 2, 1 => 1], null, 'e1b90c9311d5bd1d7fc90fd43d9bd49f'], + [[0 => 1, 1 => 1], new Color(['key2' => 'value2']), '61a579e02eb3ae787ef03ad40feb9a7d'], + ]; + } + + protected function createStep(): StepInterface + { + return new Step($this->places, $this->color, $this->transition); + } } diff --git a/tests/ValueObject/Bug/StepTest.php b/tests/ValueObject/Bug/StepTest.php new file mode 100644 index 00000000..18cafbad --- /dev/null +++ b/tests/ValueObject/Bug/StepTest.php @@ -0,0 +1,20 @@ +places, $this->color, $this->transition); + } +} From 0ca4e208db11c5c859afa73ff4b069e275d5c6b6 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 10 Apr 2022 13:11:17 +0700 Subject: [PATCH 18/85] Test State --- src/Generator/RandomGenerator.php | 5 +- src/Model/Generator/State.php | 82 +++++--------------------- src/Model/Generator/StateInterface.php | 20 ------- tests/Model/Generator/StateTest.php | 58 ++++++++++++++++++ 4 files changed, 75 insertions(+), 90 deletions(-) create mode 100644 tests/Model/Generator/StateTest.php diff --git a/src/Generator/RandomGenerator.php b/src/Generator/RandomGenerator.php index 49e3735a..1e620814 100644 --- a/src/Generator/RandomGenerator.php +++ b/src/Generator/RandomGenerator.php @@ -49,6 +49,7 @@ public function generate(TaskInterface $task): iterable $places = $this->modelHelper->getStartPlaceIds($task->getModelRevision()); $marking = $this->markingHelper->getMarking($petrinet, $places); $state = new State( + [], [$transitionId], count($task->getModelRevision()->getPlaces()), count($task->getModelRevision()->getTransitions()) @@ -93,10 +94,6 @@ protected function update(StateInterface $state, MarkingInterface $marking, int } } $state->addVisitedTransition($transitionId); - - // Update current coverage. - $state->setTransitionCoverage(count($state->getVisitedTransitions()) / $state->getTotalTransitions() * 100); - $state->setPlaceCoverage(count($state->getVisitedPlaces()) / $state->getTotalPlaces() * 100); } protected function nextTransition(PetrinetInterface $petrinet, MarkingInterface $marking): ?TransitionInterface diff --git a/src/Model/Generator/State.php b/src/Model/Generator/State.php index 9c4fc2ec..8c23b9af 100644 --- a/src/Model/Generator/State.php +++ b/src/Model/Generator/State.php @@ -2,67 +2,30 @@ namespace Tienvx\Bundle\MbtBundle\Model\Generator; +use Tienvx\Bundle\MbtBundle\Exception\OutOfRangeException; + class State implements StateInterface { - protected array $visitedPlaces = []; - protected int $totalPlaces; - protected array $visitedTransitions = []; - protected int $totalTransitions; - protected float $transitionCoverage = 0; - protected float $placeCoverage = 0; + protected float $transitionCoverage = 1; + protected float $placeCoverage = 1; public function __construct( - array $visitedTransitions, - int $totalPlaces, - int $totalTransitions + protected array $visitedPlaces = [], + protected array $visitedTransitions = [], + protected int $totalPlaces = 1, + protected int $totalTransitions = 1 ) { - $this->visitedTransitions = $visitedTransitions; - $this->totalPlaces = $totalPlaces; - $this->totalTransitions = $totalTransitions; - } - - public function getVisitedPlaces(): array - { - return $this->visitedPlaces; - } - - public function setVisitedPlaces(array $visitedPlaces): void - { - $this->visitedPlaces = []; - - foreach ($visitedPlaces as $visitedPlace) { - $this->addVisitedPlace($visitedPlace); + if ($totalPlaces <= 0 || $totalTransitions <= 0) { + throw new OutOfRangeException('State need at least 1 place and 1 transition'); } + $this->updateCoverage(); } public function addVisitedPlace(int $placeId) { if (!in_array($placeId, $this->visitedPlaces)) { $this->visitedPlaces[] = $placeId; - } - } - - public function getTotalPlaces(): int - { - return $this->totalPlaces; - } - - public function setTotalPlaces(int $totalPlaces): void - { - $this->totalPlaces = $totalPlaces; - } - - public function getVisitedTransitions(): array - { - return $this->visitedTransitions; - } - - public function setVisitedTransitions(array $visitedTransitions): void - { - $this->visitedTransitions = []; - - foreach ($visitedTransitions as $visitedTransition) { - $this->addVisitedTransition($visitedTransition); + $this->updateCoverage(); } } @@ -70,36 +33,23 @@ public function addVisitedTransition(int $transitionId) { if (!in_array($transitionId, $this->visitedTransitions)) { $this->visitedTransitions[] = $transitionId; + $this->updateCoverage(); } } - public function getTotalTransitions(): int - { - return $this->totalTransitions; - } - - public function setTotalTransitions(int $totalTransitions): void - { - $this->totalTransitions = $totalTransitions; - } - public function getTransitionCoverage(): float { return $this->transitionCoverage; } - public function setTransitionCoverage(float $transitionCoverage): void - { - $this->transitionCoverage = $transitionCoverage; - } - public function getPlaceCoverage(): float { return $this->placeCoverage; } - public function setPlaceCoverage(float $placeCoverage): void + protected function updateCoverage(): void { - $this->placeCoverage = $placeCoverage; + $this->transitionCoverage = count($this->visitedTransitions) / $this->totalTransitions * 100; + $this->placeCoverage = count($this->visitedPlaces) / $this->totalPlaces * 100; } } diff --git a/src/Model/Generator/StateInterface.php b/src/Model/Generator/StateInterface.php index 01bb9913..997cb988 100644 --- a/src/Model/Generator/StateInterface.php +++ b/src/Model/Generator/StateInterface.php @@ -4,31 +4,11 @@ interface StateInterface { - public function getVisitedPlaces(): array; - - public function setVisitedPlaces(array $visitedPlaces): void; - public function addVisitedPlace(int $placeId); - public function getTotalPlaces(): int; - - public function setTotalPlaces(int $totalPlaces): void; - - public function getVisitedTransitions(): array; - - public function setVisitedTransitions(array $visitedTransitions): void; - public function addVisitedTransition(int $transitionId); - public function getTotalTransitions(): int; - - public function setTotalTransitions(int $totalTransitions): void; - public function getTransitionCoverage(): float; - public function setTransitionCoverage(float $transitionCoverage): void; - public function getPlaceCoverage(): float; - - public function setPlaceCoverage(float $placeCoverage): void; } diff --git a/tests/Model/Generator/StateTest.php b/tests/Model/Generator/StateTest.php new file mode 100644 index 00000000..6faea4b8 --- /dev/null +++ b/tests/Model/Generator/StateTest.php @@ -0,0 +1,58 @@ +state = new State([], [], 5, 10); + } + + /** + * @dataProvider totalPlacesAndTransitionsProvider + */ + public function testMissingPlaceOrTransition(int $places, int $transitions): void + { + $this->expectExceptionObject(new OutOfRangeException('State need at least 1 place and 1 transition')); + new State([], [], $places, $transitions); + } + + public function testPlaceCoverage(): void + { + $this->assertSame(0.0, $this->state->getPlaceCoverage()); + $this->state->addVisitedPlace(1); + $this->assertSame(20.0, $this->state->getPlaceCoverage()); + } + + public function testTransitionCoverage(): void + { + $this->assertSame(0.0, $this->state->getTransitionCoverage()); + $this->state->addVisitedTransition(1); + $this->assertSame(10.0, $this->state->getTransitionCoverage()); + } + + public function totalPlacesAndTransitionsProvider(): array + { + return [ + [0, 11], + [12, 0], + [0, 0], + ]; + } +} From b4c2964b11c4e3fb0f1a9f9846ab2bb27650547c Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 10 Apr 2022 13:11:48 +0700 Subject: [PATCH 19/85] At least 1 place and 1 transition in model --- src/Entity/Model/Revision.php | 13 ++++++++++++- src/Resources/translations/validators.en.php | 1 + tests/Entity/Model/RevisionTest.php | 16 +++++++++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Entity/Model/Revision.php b/src/Entity/Model/Revision.php index 1dec850e..472e2c44 100644 --- a/src/Entity/Model/Revision.php +++ b/src/Entity/Model/Revision.php @@ -33,6 +33,7 @@ class Revision extends BaseRevision * @Assert\Type("\Tienvx\Bundle\MbtBundle\ValueObject\Model\Place") * }) * @Assert\Valid + * @Assert\Count(min=1) */ protected array $places = []; @@ -42,6 +43,7 @@ class Revision extends BaseRevision * @Assert\Type("\Tienvx\Bundle\MbtBundle\ValueObject\Model\Transition") * }) * @Assert\Valid + * @Assert\Count(min=1) */ protected array $transitions = []; @@ -53,12 +55,17 @@ public function validatePlacesInTransitions(ExecutionContextInterface $context, foreach ($this->transitions as $index => $transition) { if ($transition instanceof TransitionInterface) { $fromPlaces = $transition->getFromPlaces(); + $toPlaces = $transition->getToPlaces(); + if (!$fromPlaces && !$toPlaces) { + $context->buildViolation('mbt.model.missing_places') + ->atPath(sprintf('transitions[%d]', $index)) + ->addViolation(); + } if ($fromPlaces && array_diff($fromPlaces, array_keys($this->places))) { $context->buildViolation('mbt.model.places_invalid') ->atPath(sprintf('transitions[%d].fromPlaces', $index)) ->addViolation(); } - $toPlaces = $transition->getToPlaces(); if ($toPlaces && array_diff($toPlaces, array_keys($this->places))) { $context->buildViolation('mbt.model.places_invalid') ->atPath(sprintf('transitions[%d].toPlaces', $index)) @@ -73,6 +80,10 @@ public function validatePlacesInTransitions(ExecutionContextInterface $context, */ public function validateStartTransitions(ExecutionContextInterface $context, $payload): void { + if (count($this->transitions) === 0) { + return; + } + $startTransitions = array_filter( $this->transitions, fn ($transition) => $transition instanceof TransitionInterface && 0 === count($transition->getFromPlaces()) diff --git a/src/Resources/translations/validators.en.php b/src/Resources/translations/validators.en.php index c98cec9b..8fd8b837 100644 --- a/src/Resources/translations/validators.en.php +++ b/src/Resources/translations/validators.en.php @@ -3,6 +3,7 @@ return [ 'mbt' => [ 'model' => [ + 'missing_places' => 'Places are missing.', 'places_invalid' => 'Places are invalid.', 'missing_start_transition' => 'Missing start transition.', 'too_many_start_transitions' => 'There must be only one start transition.', diff --git a/tests/Entity/Model/RevisionTest.php b/tests/Entity/Model/RevisionTest.php index 50c17408..75dbfeb7 100644 --- a/tests/Entity/Model/RevisionTest.php +++ b/tests/Entity/Model/RevisionTest.php @@ -81,9 +81,11 @@ public function testValidateInvalidRevisionTooManyStartTransitions(): void $this->revision->getTransition(0)->setFromPlaces([]); $this->revision->getTransition(1)->setFromPlaces([]); $violations = $this->validator->validate($this->revision); - $this->assertCount(10, $violations); + $this->assertCount(11, $violations); $message = 'Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).transitions[0].toPlaces: mbt.model.places_invalid +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).transitions[1]: + mbt.model.missing_places Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).transitions: mbt.model.too_many_start_transitions Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).places[0].label: @@ -106,6 +108,18 @@ public function testValidateInvalidRevisionTooManyStartTransitions(): void $this->assertSame($message, (string) $violations); } + public function testNoPlacesAndTransitions(): void + { + $violations = $this->validator->validate($this->createRevision()); + $this->assertCount(2, $violations); + $message = 'Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).places: + This collection should contain 1 element or more. (code bef8e338-6ae5-4caf-b8e2-50e7b0579e69) +Object(Tienvx\Bundle\MbtBundle\Entity\Model\Revision).transitions: + This collection should contain 1 element or more. (code bef8e338-6ae5-4caf-b8e2-50e7b0579e69) +'; + $this->assertSame($message, (string) $violations); + } + protected function createRevision(): RevisionInterface { return new Revision(); From da678a8c83de2c2f277cf85dc6866d2cdd93110d Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 10 Apr 2022 13:12:33 +0700 Subject: [PATCH 20/85] Fix cs --- src/Entity/Model/Revision.php | 2 +- tests/Model/Generator/StateTest.php | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Entity/Model/Revision.php b/src/Entity/Model/Revision.php index 472e2c44..b758773a 100644 --- a/src/Entity/Model/Revision.php +++ b/src/Entity/Model/Revision.php @@ -80,7 +80,7 @@ public function validatePlacesInTransitions(ExecutionContextInterface $context, */ public function validateStartTransitions(ExecutionContextInterface $context, $payload): void { - if (count($this->transitions) === 0) { + if (0 === count($this->transitions)) { return; } diff --git a/tests/Model/Generator/StateTest.php b/tests/Model/Generator/StateTest.php index 6faea4b8..c1701da3 100644 --- a/tests/Model/Generator/StateTest.php +++ b/tests/Model/Generator/StateTest.php @@ -3,14 +3,9 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Model\Generator; use PHPUnit\Framework\TestCase; -use SingleColorPetrinet\Model\Color; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Exception\OutOfRangeException; -use Tienvx\Bundle\MbtBundle\Model\Bug\Step; -use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; use Tienvx\Bundle\MbtBundle\Model\Generator\State; use Tienvx\Bundle\MbtBundle\Model\Generator\StateInterface; -use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; /** * @covers \Tienvx\Bundle\MbtBundle\Model\Generator\State From c717dcaba7369dd5aa58de8ac0443d653e95cbb1 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 10 Apr 2022 13:22:43 +0700 Subject: [PATCH 21/85] Test Browser --- tests/Entity/Task/BrowserTest.php | 20 +++++++++++++++++ tests/Model/Task/BrowserTest.php | 37 +++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 tests/Entity/Task/BrowserTest.php create mode 100644 tests/Model/Task/BrowserTest.php diff --git a/tests/Entity/Task/BrowserTest.php b/tests/Entity/Task/BrowserTest.php new file mode 100644 index 00000000..aa3fbe35 --- /dev/null +++ b/tests/Entity/Task/BrowserTest.php @@ -0,0 +1,20 @@ +browser = $this->createBrowser(); + } + + public function testName(): void + { + $this->browser->setName('firefox'); + $this->assertSame('firefox', $this->browser->getName()); + } + + public function testVersion(): void + { + $this->browser->setVersion('99.0'); + $this->assertSame('99.0', $this->browser->getVersion()); + } + + protected function createBrowser(): BrowserInterface + { + return new Browser(); + } +} From 420f1f5806f7fe6ae66b3151a26f34602aa93f8d Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 10 Apr 2022 13:25:28 +0700 Subject: [PATCH 22/85] Covers Model model --- tests/Entity/ModelTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Entity/ModelTest.php b/tests/Entity/ModelTest.php index b74bcdc2..8a1afb1e 100644 --- a/tests/Entity/ModelTest.php +++ b/tests/Entity/ModelTest.php @@ -8,8 +8,8 @@ /** * @covers \Tienvx\Bundle\MbtBundle\Entity\Model + * @covers \Tienvx\Bundle\MbtBundle\Model\Model * - * @uses \Tienvx\Bundle\MbtBundle\Model\Model * @uses \Tienvx\Bundle\MbtBundle\Entity\Model\Revision * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision */ From 90d9cf852ba29e8ad14e9f3bdc2cf711ec0d1735 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 10 Apr 2022 14:02:52 +0700 Subject: [PATCH 23/85] Test Bug --- src/Model/Bug.php | 2 +- tests/Entity/BugTest.php | 47 ++++++++++++++++++++--------- tests/Model/BugTest.php | 65 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 tests/Model/BugTest.php diff --git a/src/Model/Bug.php b/src/Model/Bug.php index 20a996d7..937938d7 100644 --- a/src/Model/Bug.php +++ b/src/Model/Bug.php @@ -5,7 +5,7 @@ use DateTimeInterface; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; -abstract class Bug implements BugInterface +class Bug implements BugInterface { protected ?int $id = null; protected string $title; diff --git a/tests/Entity/BugTest.php b/tests/Entity/BugTest.php index 5cd80c7e..73a15d6f 100644 --- a/tests/Entity/BugTest.php +++ b/tests/Entity/BugTest.php @@ -2,43 +2,62 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Entity; -use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Validation; use Tienvx\Bundle\MbtBundle\Entity\Bug; +use Tienvx\Bundle\MbtBundle\Model\BugInterface; +use Tienvx\Bundle\MbtBundle\Tests\Model\BugTest as BugModelTest; /** * @covers \Tienvx\Bundle\MbtBundle\Entity\Bug * @covers \Tienvx\Bundle\MbtBundle\Model\Bug + * + * @uses \Tienvx\Bundle\MbtBundle\Entity\Task + * @uses \Tienvx\Bundle\MbtBundle\Model\Task + * @uses \Tienvx\Bundle\MbtBundle\ValueObject\Bug\Step + * @uses \Tienvx\Bundle\MbtBundle\Model\Bug\Step + * @uses \Tienvx\Bundle\MbtBundle\Entity\Progress + * @uses \Tienvx\Bundle\MbtBundle\Model\Progress */ -class BugTest extends TestCase +class BugTest extends BugModelTest { public function testPrePersist(): void { - $bug = new Bug(); - $bug->prePersist(); - $this->assertInstanceOf(\DateTime::class, $bug->getCreatedAt()); - $this->assertInstanceOf(\DateTime::class, $bug->getUpdatedAt()); + $this->bug->prePersist(); + $this->assertInstanceOf(\DateTime::class, $this->bug->getCreatedAt()); + $this->assertInstanceOf(\DateTime::class, $this->bug->getUpdatedAt()); } public function testPreUpdate(): void { - $bug = new Bug(); - $bug->prePersist(); - $bug->preUpdate(); - $this->assertInstanceOf(\DateTime::class, $bug->getUpdatedAt()); + $this->bug->prePersist(); + $this->bug->preUpdate(); + $this->assertInstanceOf(\DateTime::class, $updatedAt = $this->bug->getUpdatedAt()); + $this->bug->preUpdate(); + $this->assertTrue( + $this->bug->getUpdatedAt() instanceof \DateTime + && $updatedAt !== $this->bug->getUpdatedAt() + ); } public function testValidateInvalidBug(): void { - $bug = new Bug(); - $bug->setTitle(''); + $this->bug->setTitle(''); $validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator(); - $violations = $validator->validate($bug); - $this->assertCount(1, $violations); + $violations = $validator->validate($this->bug); + $this->assertCount(3, $violations); $message = 'Object(Tienvx\Bundle\MbtBundle\Entity\Bug).title: This value should not be blank. (code c1051bb4-d103-4f74-8988-acbcafc7fdc3) +Object(Tienvx\Bundle\MbtBundle\Entity\Bug).steps[0].places: + mbt.bug.missing_places_in_step (code bef8e338-6ae5-4caf-b8e2-50e7b0579e69) +Object(Tienvx\Bundle\MbtBundle\Entity\Bug).steps[1].places: + mbt.bug.missing_places_in_step (code bef8e338-6ae5-4caf-b8e2-50e7b0579e69) '; $this->assertSame($message, (string) $violations); } + + protected function createBug(): BugInterface + { + return new Bug(); + } } diff --git a/tests/Model/BugTest.php b/tests/Model/BugTest.php new file mode 100644 index 00000000..8b975b7a --- /dev/null +++ b/tests/Model/BugTest.php @@ -0,0 +1,65 @@ +steps = [ + new Step([], new Color(), 0), + new Step([], new Color(), 1), + ]; + $this->task = new Task(); + $this->progress = new Progress(); + $this->bug = $this->createBug(); + $this->bug->setId(123); + $this->bug->setTitle('bug title'); + $this->bug->setSteps($this->steps); + $this->bug->setTask($this->task); + $this->bug->setMessage('bug message'); + $this->bug->setProgress($this->progress); + $this->bug->setClosed(true); + } + + public function testProperties(): void + { + $this->assertSame(123, $this->bug->getId()); + $this->assertSame('bug title', $this->bug->getTitle()); + $this->assertSame($this->steps, $this->bug->getSteps()); + $this->assertSame($this->task, $this->bug->getTask()); + $this->assertSame('bug message', $this->bug->getMessage()); + $this->assertSame($this->progress, $this->bug->getProgress()); + $this->assertSame(true, $this->bug->isClosed()); + } + + protected function createBug(): BugInterface + { + return new Bug(); + } +} From 04ffa8d46ca6ca34a12352af7599c59f7ce1e95d Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 10 Apr 2022 14:30:53 +0700 Subject: [PATCH 24/85] Test Task --- src/Entity/Task.php | 6 ---- src/Model/Task.php | 8 ++++- tests/Entity/TaskTest.php | 54 +++++++++++++++++++++++++++------ tests/Model/TaskTest.php | 64 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 17 deletions(-) create mode 100644 tests/Model/TaskTest.php diff --git a/src/Entity/Task.php b/src/Entity/Task.php index bf7b2e76..514abae8 100644 --- a/src/Entity/Task.php +++ b/src/Entity/Task.php @@ -4,7 +4,6 @@ use DateTime; use DateTimeInterface; -use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping\Embedded; @@ -78,11 +77,6 @@ class Task extends TaskModel */ protected DateTimeInterface $updatedAt; - public function __construct() - { - $this->bugs = new ArrayCollection(); - } - /** * @ORM\PrePersist */ diff --git a/src/Model/Task.php b/src/Model/Task.php index 85b2805e..5cb5244e 100644 --- a/src/Model/Task.php +++ b/src/Model/Task.php @@ -3,6 +3,7 @@ namespace Tienvx\Bundle\MbtBundle\Model; use DateTimeInterface; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; use Tienvx\Bundle\MbtBundle\Model\Task\BrowserInterface; @@ -20,6 +21,11 @@ class Task implements TaskInterface protected DateTimeInterface $updatedAt; protected DateTimeInterface $createdAt; + public function __construct() + { + $this->bugs = new ArrayCollection(); + } + public function setId(int $id) { $this->id = $id; @@ -81,7 +87,7 @@ public function setBrowser(BrowserInterface $browser): void } /** - * @return Collection|BugInterface[] + * @return Collection */ public function getBugs(): Collection { diff --git a/tests/Entity/TaskTest.php b/tests/Entity/TaskTest.php index fe1af0be..c215ccb7 100644 --- a/tests/Entity/TaskTest.php +++ b/tests/Entity/TaskTest.php @@ -2,28 +2,62 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Entity; -use PHPUnit\Framework\TestCase; +use Symfony\Component\Validator\Validation; use Tienvx\Bundle\MbtBundle\Entity\Task; +use Tienvx\Bundle\MbtBundle\Model\TaskInterface; +use Tienvx\Bundle\MbtBundle\Tests\Model\TaskTest as TaskModelTest; /** * @covers \Tienvx\Bundle\MbtBundle\Entity\Task * @covers \Tienvx\Bundle\MbtBundle\Model\Task + * + * @uses \Tienvx\Bundle\MbtBundle\Model\Bug + * @uses \Tienvx\Bundle\MbtBundle\Entity\Bug + * @uses \Tienvx\Bundle\MbtBundle\Entity\Model\Revision + * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision + * @uses \Tienvx\Bundle\MbtBundle\Entity\Task\Browser + * @uses \Tienvx\Bundle\MbtBundle\Model\Task\Browser */ -class TaskTest extends TestCase +class TaskTest extends TaskModelTest { public function testPrePersist(): void { - $task = new Task(); - $task->prePersist(); - $this->assertInstanceOf(\DateTime::class, $task->getCreatedAt()); - $this->assertInstanceOf(\DateTime::class, $task->getUpdatedAt()); + $this->task->prePersist(); + $this->assertInstanceOf(\DateTime::class, $this->task->getCreatedAt()); + $this->assertInstanceOf(\DateTime::class, $this->task->getUpdatedAt()); } public function testPreUpdate(): void { - $task = new Task(); - $task->prePersist(); - $task->preUpdate(); - $this->assertInstanceOf(\DateTime::class, $task->getUpdatedAt()); + $this->task->prePersist(); + $this->task->preUpdate(); + $this->assertInstanceOf(\DateTime::class, $updatedAt = $this->task->getUpdatedAt()); + $this->task->preUpdate(); + $this->assertTrue( + $this->task->getUpdatedAt() instanceof \DateTime + && $updatedAt !== $this->task->getUpdatedAt() + ); + } + + public function testValidateInvalidBug(): void + { + $this->task->setTitle(''); + + $validator = Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator(); + $violations = $validator->validate($this->task); + $this->assertCount(3, $violations); + $message = 'Object(Tienvx\Bundle\MbtBundle\Entity\Task).title: + This value should not be blank. (code c1051bb4-d103-4f74-8988-acbcafc7fdc3) +Object(Tienvx\Bundle\MbtBundle\Entity\Task).modelRevision.places: + This collection should contain 1 element or more. (code bef8e338-6ae5-4caf-b8e2-50e7b0579e69) +Object(Tienvx\Bundle\MbtBundle\Entity\Task).modelRevision.transitions: + This collection should contain 1 element or more. (code bef8e338-6ae5-4caf-b8e2-50e7b0579e69) +'; + $this->assertSame($message, (string) $violations); + } + + protected function createTask(): TaskInterface + { + return new Task(); } } diff --git a/tests/Model/TaskTest.php b/tests/Model/TaskTest.php new file mode 100644 index 00000000..c3aed8f7 --- /dev/null +++ b/tests/Model/TaskTest.php @@ -0,0 +1,64 @@ +bugs = [ + new Bug(), + ]; + $this->revision = new Revision(); + $this->browser = new Browser(); + $this->task = $this->createTask(); + $this->task->setId(123); + $this->task->setTitle('bug title'); + $this->task->setModelRevision($this->revision); + $this->task->setAuthor(12); + $this->task->setRunning(true); + $this->task->setBrowser($this->browser); + $this->task->addBug($this->bugs[0]); + $this->task->setDebug(true); + } + + public function testProperties(): void + { + $this->assertSame(123, $this->task->getId()); + $this->assertSame('bug title', $this->task->getTitle()); + $this->assertSame($this->revision, $this->task->getModelRevision()); + $this->assertSame(12, $this->task->getAuthor()); + $this->assertSame(true, $this->task->isRunning()); + $this->assertSame($this->browser, $this->task->getBrowser()); + $this->assertSame($this->bugs, $this->task->getBugs()->toArray()); + $this->assertSame(true, $this->task->isDebug()); + } + + protected function createTask(): TaskInterface + { + return new Task(); + } +} From 59eaaa28d02435fbdba6ba4724ba3cf1e6118966 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 10 Apr 2022 14:47:58 +0700 Subject: [PATCH 25/85] Test model value objects --- tests/Model/Model/Revision/CommandTest.php | 13 ++++++++--- tests/Model/Model/Revision/PlaceTest.php | 13 ++++++++--- tests/Model/Model/Revision/TransitionTest.php | 13 ++++++++--- tests/ValueObject/Model/CommandTest.php | 20 +++++++++++++++++ tests/ValueObject/Model/PlaceTest.php | 22 +++++++++++++++++++ tests/ValueObject/Model/TransitionTest.php | 22 +++++++++++++++++++ 6 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 tests/ValueObject/Model/CommandTest.php create mode 100644 tests/ValueObject/Model/PlaceTest.php create mode 100644 tests/ValueObject/Model/TransitionTest.php diff --git a/tests/Model/Model/Revision/CommandTest.php b/tests/Model/Model/Revision/CommandTest.php index 82e6df46..ce137e59 100644 --- a/tests/Model/Model/Revision/CommandTest.php +++ b/tests/Model/Model/Revision/CommandTest.php @@ -17,7 +17,7 @@ class CommandTest extends TestCase protected function setUp(): void { - $this->command = new Command(); + $this->command = $this->createCommand(); $this->command->setCommand(WindowCommandRunner::OPEN); $this->command->setTarget('http://localhost:1234'); $this->command->setValue('123'); @@ -25,17 +25,24 @@ protected function setUp(): void public function testSerialize(): void { + $className = get_class($this->command); // phpcs:ignore Generic.Files.LineLength - $this->assertSame('O:52:"Tienvx\Bundle\MbtBundle\Model\Model\Revision\Command":3:{s:7:"command";s:4:"open";s:6:"target";s:21:"http://localhost:1234";s:5:"value";s:3:"123";}', serialize($this->command)); + $this->assertSame('O:' . strlen($className) . ':"' . $className . '":3:{s:7:"command";s:4:"open";s:6:"target";s:21:"http://localhost:1234";s:5:"value";s:3:"123";}', serialize($this->command)); } public function testUnerialize(): void { + $className = get_class($this->command); // phpcs:ignore Generic.Files.LineLength - $command = unserialize('O:52:"Tienvx\Bundle\MbtBundle\Model\Model\Revision\Command":3:{s:7:"command";s:5:"click";s:6:"target";s:11:"css=.button";s:5:"value";N;}'); + $command = unserialize('O:' . strlen($className) . ':"' . $className . '":3:{s:7:"command";s:5:"click";s:6:"target";s:11:"css=.button";s:5:"value";N;}'); $this->assertInstanceOf(CommandInterface::class, $command); $this->assertSame(MouseCommandRunner::CLICK, $command->getCommand()); $this->assertSame('css=.button', $command->getTarget()); $this->assertSame(null, $command->getValue()); } + + protected function createCommand(): CommandInterface + { + return new Command(); + } } diff --git a/tests/Model/Model/Revision/PlaceTest.php b/tests/Model/Model/Revision/PlaceTest.php index 954702ce..0ce2e1fb 100644 --- a/tests/Model/Model/Revision/PlaceTest.php +++ b/tests/Model/Model/Revision/PlaceTest.php @@ -24,7 +24,7 @@ class PlaceTest extends TestCase protected function setUp(): void { $this->setUpCommands(); - $this->place = new Place(); + $this->place = $this->createPlace(); $this->place->setLabel('place label'); $this->place->setCommands([ $this->command1, @@ -46,14 +46,16 @@ protected function setUpCommands(): void public function testSerialize(): void { + $className = get_class($this->place); // phpcs:ignore Generic.Files.LineLength - $this->assertSame('O:50:"Tienvx\Bundle\MbtBundle\Model\Model\Revision\Place":2:{s:5:"label";s:11:"place label";s:8:"commands";a:2:{i:0;a:3:{s:7:"command";s:10:"assertText";s:6:"target";s:10:"css=.title";s:5:"value";s:5:"Hello";}i:1;a:3:{s:7:"command";s:11:"assertAlert";s:6:"target";s:12:"css=.warning";s:5:"value";s:13:"Are you sure?";}}}', serialize($this->place)); + $this->assertSame('O:' . strlen($className) . ':"' . $className . '":2:{s:5:"label";s:11:"place label";s:8:"commands";a:2:{i:0;a:3:{s:7:"command";s:10:"assertText";s:6:"target";s:10:"css=.title";s:5:"value";s:5:"Hello";}i:1;a:3:{s:7:"command";s:11:"assertAlert";s:6:"target";s:12:"css=.warning";s:5:"value";s:13:"Are you sure?";}}}', serialize($this->place)); } public function testUnerialize(): void { + $className = get_class($this->place); // phpcs:ignore Generic.Files.LineLength - $place = unserialize('O:50:"Tienvx\Bundle\MbtBundle\Model\Model\Revision\Place":2:{s:5:"label";s:10:"Serialized";s:8:"commands";a:1:{i:0;a:3:{s:7:"command";s:19:"assertSelectedValue";s:6:"target";s:12:"css=.country";s:5:"value";s:2:"vn";}}}'); + $place = unserialize('O:' . strlen($className) . ':"' . $className . '":2:{s:5:"label";s:10:"Serialized";s:8:"commands";a:1:{i:0;a:3:{s:7:"command";s:19:"assertSelectedValue";s:6:"target";s:12:"css=.country";s:5:"value";s:2:"vn";}}}'); $this->assertInstanceOf(PlaceInterface::class, $place); $this->assertSame('Serialized', $place->getLabel()); $this->assertInstanceOf(CommandInterface::class, $place->getCommands()[0]); @@ -61,4 +63,9 @@ public function testUnerialize(): void $this->assertSame('css=.country', $place->getCommands()[0]->getTarget()); $this->assertSame('vn', $place->getCommands()[0]->getValue()); } + + protected function createPlace(): PlaceInterface + { + return new Place(); + } } diff --git a/tests/Model/Model/Revision/TransitionTest.php b/tests/Model/Model/Revision/TransitionTest.php index ab82c66e..3c6f4676 100644 --- a/tests/Model/Model/Revision/TransitionTest.php +++ b/tests/Model/Model/Revision/TransitionTest.php @@ -26,7 +26,7 @@ class TransitionTest extends TestCase protected function setUp(): void { $this->setUpCommands(); - $this->transition = new Transition(); + $this->transition = $this->createTransition(); $this->transition->setLabel('transition label'); $this->transition->setGuard('count > 2'); $this->transition->setFromPlaces([1, 2, 3]); @@ -51,14 +51,16 @@ protected function setUpCommands(): void public function testSerialize(): void { + $className = get_class($this->transition); // phpcs:ignore Generic.Files.LineLength - $this->assertSame('O:55:"Tienvx\Bundle\MbtBundle\Model\Model\Revision\Transition":5:{s:5:"label";s:16:"transition label";s:5:"guard";s:9:"count > 2";s:10:"fromPlaces";a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}s:8:"toPlaces";a:2:{i:0;i:12;i:1;i:23;}s:8:"commands";a:2:{i:0;a:3:{s:7:"command";s:4:"type";s:6:"target";s:10:"css=.email";s:5:"value";s:16:"test@example.com";}i:1;a:3:{s:7:"command";s:5:"click";s:6:"target";s:9:"css=.link";s:5:"value";N;}}}', serialize($this->transition)); + $this->assertSame('O:' . strlen($className) . ':"' . $className . '":5:{s:5:"label";s:16:"transition label";s:5:"guard";s:9:"count > 2";s:10:"fromPlaces";a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}s:8:"toPlaces";a:2:{i:0;i:12;i:1;i:23;}s:8:"commands";a:2:{i:0;a:3:{s:7:"command";s:4:"type";s:6:"target";s:10:"css=.email";s:5:"value";s:16:"test@example.com";}i:1;a:3:{s:7:"command";s:5:"click";s:6:"target";s:9:"css=.link";s:5:"value";N;}}}', serialize($this->transition)); } public function testUnerialize(): void { + $className = get_class($this->transition); // phpcs:ignore Generic.Files.LineLength - $transition = unserialize('O:55:"Tienvx\Bundle\MbtBundle\Model\Model\Revision\Transition":5:{s:5:"label";s:10:"Serialized";s:5:"guard";s:10:"count == 3";s:10:"fromPlaces";a:2:{i:0;i:1;i:1;i:4;}s:8:"toPlaces";a:1:{i:0;i:15;}s:8:"commands";a:1:{i:0;a:3:{s:7:"command";s:5:"store";s:6:"target";s:2:"55";s:5:"value";s:6:"number";}}}'); + $transition = unserialize('O:' . strlen($className) . ':"' . $className . '":5:{s:5:"label";s:10:"Serialized";s:5:"guard";s:10:"count == 3";s:10:"fromPlaces";a:2:{i:0;i:1;i:1;i:4;}s:8:"toPlaces";a:1:{i:0;i:15;}s:8:"commands";a:1:{i:0;a:3:{s:7:"command";s:5:"store";s:6:"target";s:2:"55";s:5:"value";s:6:"number";}}}'); $this->assertInstanceOf(TransitionInterface::class, $transition); $this->assertSame('Serialized', $transition->getLabel()); $this->assertSame('count == 3', $transition->getGuard()); @@ -69,4 +71,9 @@ public function testUnerialize(): void $this->assertSame('55', $transition->getCommands()[0]->getTarget()); $this->assertSame('number', $transition->getCommands()[0]->getValue()); } + + protected function createTransition(): TransitionInterface + { + return new Transition(); + } } diff --git a/tests/ValueObject/Model/CommandTest.php b/tests/ValueObject/Model/CommandTest.php new file mode 100644 index 00000000..9d2460cc --- /dev/null +++ b/tests/ValueObject/Model/CommandTest.php @@ -0,0 +1,20 @@ + Date: Sun, 10 Apr 2022 15:00:13 +0700 Subject: [PATCH 26/85] Reuse services --- tests/Service/Petrinet/MarkingHelperTest.php | 21 +++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/Service/Petrinet/MarkingHelperTest.php b/tests/Service/Petrinet/MarkingHelperTest.php index 7538d093..fcaa894a 100644 --- a/tests/Service/Petrinet/MarkingHelperTest.php +++ b/tests/Service/Petrinet/MarkingHelperTest.php @@ -9,18 +9,27 @@ use SingleColorPetrinet\Builder\SingleColorPetrinetBuilder; use SingleColorPetrinet\Model\Color; use SingleColorPetrinet\Model\ColorfulFactory; +use SingleColorPetrinet\Model\ColorfulFactoryInterface; use SingleColorPetrinet\Model\Place; use Tienvx\Bundle\MbtBundle\Service\Petrinet\MarkingHelper; +use Tienvx\Bundle\MbtBundle\Service\Petrinet\MarkingHelperInterface; /** * @covers \Tienvx\Bundle\MbtBundle\Service\Petrinet\MarkingHelper */ class MarkingHelperTest extends TestCase { + protected ColorfulFactoryInterface $factory; + protected MarkingHelperInterface $helper; + + protected function setUp(): void + { + $this->factory = new ColorfulFactory(); + $this->helper = new MarkingHelper($this->factory); + } + public function testGetPlaces(): void { - $factory = new ColorfulFactory(); - $helper = new MarkingHelper($factory); $pm1 = new PlaceMarking(); $pm2 = new PlaceMarking(); $pm1->setPlace($p1 = new Place()); @@ -37,14 +46,12 @@ public function testGetPlaces(): void $this->assertSame([ 1 => 1, 2 => 3, - ], $helper->getPlaces($marking)); + ], $this->helper->getPlaces($marking)); } public function testGetMarking(): void { - $factory = new ColorfulFactory(); - $helper = new MarkingHelper($factory); - $builder = new SingleColorPetrinetBuilder($factory); + $builder = new SingleColorPetrinetBuilder($this->factory); $petrinet = $builder ->connect($place1 = $builder->place(), $transition1 = $builder->transition()) @@ -53,7 +60,7 @@ public function testGetMarking(): void ->connect($transition2, $place4 = $builder->place()) ->getPetrinet(); - $marking = $helper->getMarking($petrinet, [0 => 1, 2 => 3], new Color(['key' => 'value'])); + $marking = $this->helper->getMarking($petrinet, [0 => 1, 2 => 3], new Color(['key' => 'value'])); $this->assertSame(['key' => 'value'], $marking->getColor()->getValues()); $this->assertCount(1, $marking->getPlaceMarking($place1)->getTokens()); $this->assertNull($marking->getPlaceMarking($place2)); From c4044cb1bdfe1bc937236bef85287547bcb7422e Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 10 Apr 2022 15:08:02 +0700 Subject: [PATCH 27/85] Test dispatch too short steps --- tests/Reducer/DispatcherTestCase.php | 12 ++++++++++-- tests/Reducer/Split/SplitDispatcherTest.php | 18 ++++++++++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/tests/Reducer/DispatcherTestCase.php b/tests/Reducer/DispatcherTestCase.php index 8ef5600a..e6caa2ba 100644 --- a/tests/Reducer/DispatcherTestCase.php +++ b/tests/Reducer/DispatcherTestCase.php @@ -28,6 +28,14 @@ protected function setUp(): void $this->bug->setSteps(array_map(fn () => $this->createMock(StepInterface::class), range(1, 11))); } + public function testDispatchTooShortSteps(): void + { + $this->bug->setSteps([$this->createMock(StepInterface::class)]); + $this->messageBus->expects($this->never())->method('dispatch'); + $this->assertSame(0, $this->dispatcher->dispatch($this->bug)); + $this->assertPairs(0); + } + public function testDispatch(): void { $this->messageBus @@ -63,8 +71,8 @@ protected function assertMessage(): Callback }); } - protected function assertPairs(): void + protected function assertPairs(int $count = 4): void { - $this->assertCount(4, $this->pairs); + $this->assertCount($count, $this->pairs); } } diff --git a/tests/Reducer/Split/SplitDispatcherTest.php b/tests/Reducer/Split/SplitDispatcherTest.php index 852ad0d1..54ab7f40 100644 --- a/tests/Reducer/Split/SplitDispatcherTest.php +++ b/tests/Reducer/Split/SplitDispatcherTest.php @@ -22,14 +22,16 @@ protected function setUp(): void $this->dispatcher = new SplitDispatcher($this->messageBus); } - protected function assertPairs(): void + protected function assertPairs(int $count = 4): void { - parent::assertPairs(); - $this->assertSame([ - [0, 3], - [3, 6], - [6, 9], - [9, 10], - ], $this->pairs); + parent::assertPairs($count); + if (4 === $count) { + $this->assertSame([ + [0, 3], + [3, 6], + [6, 9], + [9, 10], + ], $this->pairs); + } } } From a1487f858bdf6c59cb885c283defc193d9b67fdc Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 10 Apr 2022 15:08:15 +0700 Subject: [PATCH 28/85] Change to abstract method --- src/Reducer/DispatcherTemplate.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Reducer/DispatcherTemplate.php b/src/Reducer/DispatcherTemplate.php index af06792f..fd083d55 100644 --- a/src/Reducer/DispatcherTemplate.php +++ b/src/Reducer/DispatcherTemplate.php @@ -33,10 +33,7 @@ public function dispatch(BugInterface $bug): int return count($pairs); } - protected function getPairs(array $steps): array - { - return []; - } + abstract protected function getPairs(array $steps): array; protected function maxPairs(array $steps): int { From 89d0bf038dba5026e3569e2fc00bcd8e77308c3d Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 11 Apr 2022 21:30:13 +0700 Subject: [PATCH 29/85] Allow record video for bug manually --- src/Entity/Bug.php | 5 +++++ src/Model/Bug.php | 11 +++++++++++ src/Model/BugInterface.php | 4 ++++ src/Repository/BugRepository.php | 14 ++++++++++++++ src/Repository/BugRepositoryInterface.php | 4 ++++ src/Service/Bug/BugHelper.php | 10 ++++++++++ src/Service/Task/TaskHelper.php | 6 ++++-- tests/Model/BugTest.php | 2 ++ tests/Repository/BugRepositoryTest.php | 20 ++++++++++++++++++++ tests/Service/Bug/BugHelperTest.php | 12 ++++++++++++ tests/Service/Task/TaskHelperTest.php | 6 +++--- 11 files changed, 89 insertions(+), 5 deletions(-) diff --git a/src/Entity/Bug.php b/src/Entity/Bug.php index 121222fd..cc6972fb 100644 --- a/src/Entity/Bug.php +++ b/src/Entity/Bug.php @@ -60,6 +60,11 @@ class Bug extends BugModel */ protected bool $closed = false; + /** + * @ORM\Column(type="boolean") + */ + protected bool $recording = false; + /** * @ORM\Column(name="created_at", type="datetime") */ diff --git a/src/Model/Bug.php b/src/Model/Bug.php index 937938d7..c12d249e 100644 --- a/src/Model/Bug.php +++ b/src/Model/Bug.php @@ -14,6 +14,7 @@ class Bug implements BugInterface protected string $message; protected ProgressInterface $progress; protected bool $closed = false; + protected bool $recording = false; protected DateTimeInterface $updatedAt; protected DateTimeInterface $createdAt; @@ -100,6 +101,16 @@ public function setClosed(bool $closed): void $this->closed = $closed; } + public function isRecording(): bool + { + return $this->recording; + } + + public function setRecording(bool $recording): void + { + $this->recording = $recording; + } + public function setCreatedAt(DateTimeInterface $createdAt): void { $this->createdAt = $createdAt; diff --git a/src/Model/BugInterface.php b/src/Model/BugInterface.php index 730d85e6..91a3417c 100644 --- a/src/Model/BugInterface.php +++ b/src/Model/BugInterface.php @@ -40,6 +40,10 @@ public function isClosed(): bool; public function setClosed(bool $closed): void; + public function isRecording(): bool; + + public function setRecording(bool $recording): void; + public function setCreatedAt(DateTimeInterface $createdAt): void; public function getCreatedAt(): DateTimeInterface; diff --git a/src/Repository/BugRepository.php b/src/Repository/BugRepository.php index 7b08e485..f0c6b466 100644 --- a/src/Repository/BugRepository.php +++ b/src/Repository/BugRepository.php @@ -51,4 +51,18 @@ public function increaseTotal(BugInterface $bug, int $total): void $bug->getProgress()->setTotal($bug->getProgress()->getTotal() + $total); }); } + + public function startRecording(BugInterface $bug): void + { + $bug->setRecording(true); + $this->getEntityManager()->flush(); + } + + public function stopRecording(BugInterface $bug): void + { + $bug->setRecording(false); + // Recording bug may take long time. Reconnect to flush changes. + $this->getEntityManager()->getConnection()->connect(); + $this->getEntityManager()->flush(); + } } diff --git a/src/Repository/BugRepositoryInterface.php b/src/Repository/BugRepositoryInterface.php index 39a8c393..f834c7c8 100644 --- a/src/Repository/BugRepositoryInterface.php +++ b/src/Repository/BugRepositoryInterface.php @@ -12,4 +12,8 @@ public function updateSteps(BugInterface $bug, array $newSteps): void; public function increaseProcessed(BugInterface $bug, int $processed = 1): void; public function increaseTotal(BugInterface $bug, int $total): void; + + public function startRecording(BugInterface $bug): void; + + public function stopRecording(BugInterface $bug): void; } diff --git a/src/Service/Bug/BugHelper.php b/src/Service/Bug/BugHelper.php index 4ceadc99..b8aa9e28 100644 --- a/src/Service/Bug/BugHelper.php +++ b/src/Service/Bug/BugHelper.php @@ -2,6 +2,7 @@ namespace Tienvx\Bundle\MbtBundle\Service\Bug; +use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException; use Symfony\Component\Messenger\MessageBusInterface; use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Exception\ExceptionInterface; @@ -85,7 +86,16 @@ public function reportBug(int $bugId): void public function recordVideo(int $bugId): void { $bug = $this->getBug($bugId, 'record video for bug'); + + if ($bug->isRecording()) { + throw new RecoverableMessageHandlingException( + sprintf('Can not record video for bug %d: bug is recording. Will retry later', $bug->getId()) + ); + } + + $this->bugRepository->startRecording($bug); $this->stepsRunner->run($bug->getSteps(), $bug, true); + $this->bugRepository->stopRecording($bug); } public function createBug(array $steps, string $message): BugInterface diff --git a/src/Service/Task/TaskHelper.php b/src/Service/Task/TaskHelper.php index fb8b4bbc..52128fa1 100644 --- a/src/Service/Task/TaskHelper.php +++ b/src/Service/Task/TaskHelper.php @@ -2,8 +2,8 @@ namespace Tienvx\Bundle\MbtBundle\Service\Task; +use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException; use Throwable; -use Tienvx\Bundle\MbtBundle\Exception\RuntimeException; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; use Tienvx\Bundle\MbtBundle\Generator\GeneratorManagerInterface; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; @@ -44,7 +44,9 @@ public function run(int $taskId): void } if ($task->isRunning()) { - throw new RuntimeException(sprintf('Can not run task %d: task is already running', $task->getId())); + throw new RecoverableMessageHandlingException( + sprintf('Can not run task %d: task is running. Will retry later', $task->getId()) + ); } $this->taskRepository->startRunning($task); diff --git a/tests/Model/BugTest.php b/tests/Model/BugTest.php index 8b975b7a..8b712a2c 100644 --- a/tests/Model/BugTest.php +++ b/tests/Model/BugTest.php @@ -45,6 +45,7 @@ protected function setUp(): void $this->bug->setMessage('bug message'); $this->bug->setProgress($this->progress); $this->bug->setClosed(true); + $this->bug->setRecording(true); } public function testProperties(): void @@ -56,6 +57,7 @@ public function testProperties(): void $this->assertSame('bug message', $this->bug->getMessage()); $this->assertSame($this->progress, $this->bug->getProgress()); $this->assertSame(true, $this->bug->isClosed()); + $this->assertSame(true, $this->bug->isRecording()); } protected function createBug(): BugInterface diff --git a/tests/Repository/BugRepositoryTest.php b/tests/Repository/BugRepositoryTest.php index b7a89409..21d97f30 100644 --- a/tests/Repository/BugRepositoryTest.php +++ b/tests/Repository/BugRepositoryTest.php @@ -2,6 +2,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Repository; +use Doctrine\DBAL\Connection; use Doctrine\DBAL\LockMode; use Doctrine\ORM\Decorator\EntityManagerDecorator; use Doctrine\ORM\Mapping\ClassMetadata; @@ -150,4 +151,23 @@ public function testIncreaseTotal(): void $this->assertSame(5, $this->bug->getProgress()->getProcessed()); $this->assertSame(13, $this->bug->getProgress()->getTotal()); } + + public function testStartRecordingBug(): void + { + $this->bug->setRecording(false); + $this->manager->expects($this->once())->method('flush'); + $this->bugRepository->startRecording($this->bug); + $this->assertTrue($this->bug->isRecording()); + } + + public function testStopRecordingBug(): void + { + $connection = $this->createMock(Connection::class); + $connection->expects($this->once())->method('connect'); + $this->bug->setRecording(true); + $this->manager->expects($this->once())->method('flush'); + $this->manager->expects($this->once())->method('getConnection')->willReturn($connection); + $this->bugRepository->stopRecording($this->bug); + $this->assertFalse($this->bug->isRecording()); + } } diff --git a/tests/Service/Bug/BugHelperTest.php b/tests/Service/Bug/BugHelperTest.php index b604ab54..9af48b9e 100644 --- a/tests/Service/Bug/BugHelperTest.php +++ b/tests/Service/Bug/BugHelperTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException; use Symfony\Component\Messenger\MessageBusInterface; use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Entity\Progress; @@ -232,6 +233,15 @@ public function testRecordVideoMissingBug(): void $this->helper->recordVideo(123); } + public function testRunTaskAlreadyRunning(): void + { + $this->expectException(RecoverableMessageHandlingException::class); + $this->expectExceptionMessage('Can not record video for bug 123: bug is recording. Will retry later'); + $this->bug->setRecording(true); + $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn($this->bug); + $this->helper->recordVideo(123); + } + public function testRecordVideo(): void { $this->stepsRunner @@ -239,6 +249,8 @@ public function testRecordVideo(): void ->method('run') ->with($this->bug->getSteps(), $this->bug, true); $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn($this->bug); + $this->bugRepository->expects($this->once())->method('startRecording')->with($this->bug); + $this->bugRepository->expects($this->once())->method('stopRecording')->with($this->bug); $this->helper->recordVideo(123); } } diff --git a/tests/Service/Task/TaskHelperTest.php b/tests/Service/Task/TaskHelperTest.php index f8d4b74d..d68de505 100644 --- a/tests/Service/Task/TaskHelperTest.php +++ b/tests/Service/Task/TaskHelperTest.php @@ -5,10 +5,10 @@ use Exception; use PHPUnit\Framework\TestCase; use SingleColorPetrinet\Model\Color; +use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException; use Throwable; use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Entity\Task; -use Tienvx\Bundle\MbtBundle\Exception\RuntimeException; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; use Tienvx\Bundle\MbtBundle\Generator\GeneratorInterface; use Tienvx\Bundle\MbtBundle\Generator\GeneratorManagerInterface; @@ -82,8 +82,8 @@ public function testRunNoTask(): void public function testRunTaskAlreadyRunning(): void { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Can not run task 123: task is already running'); + $this->expectException(RecoverableMessageHandlingException::class); + $this->expectExceptionMessage('Can not run task 123: task is running. Will retry later'); $this->task->setRunning(true); $this->taskRepository->expects($this->once())->method('find')->with(123)->willReturn($this->task); $this->taskHelper->run(123); From 1adae72130d5ba6312b162128887bd66becfac3e Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 12 Apr 2022 22:08:07 +0700 Subject: [PATCH 30/85] Capture error message on failed recording --- src/Entity/Bug.php | 7 ++-- src/Entity/Bug/Video.php | 23 +++++++++++++ src/Model/Bug.php | 13 +++++--- src/Model/Bug/Video.php | 29 +++++++++++++++++ src/Model/Bug/VideoInterface.php | 14 ++++++++ src/Model/BugInterface.php | 5 +-- src/Repository/BugRepository.php | 10 ++++-- src/Repository/BugRepositoryInterface.php | 2 ++ src/Service/Bug/BugHelper.php | 7 ++-- tests/Entity/Bug/VideoTest.php | 20 ++++++++++++ tests/Model/Bug/VideoTest.php | 39 +++++++++++++++++++++++ tests/Model/BugTest.php | 7 ++-- tests/Repository/BugRepositoryTest.php | 17 +++++++--- tests/Service/Bug/BugHelperTest.php | 39 +++++++++++++++++++++-- 14 files changed, 210 insertions(+), 22 deletions(-) create mode 100644 src/Entity/Bug/Video.php create mode 100644 src/Model/Bug/Video.php create mode 100644 src/Model/Bug/VideoInterface.php create mode 100644 tests/Entity/Bug/VideoTest.php create mode 100644 tests/Model/Bug/VideoTest.php diff --git a/src/Entity/Bug.php b/src/Entity/Bug.php index cc6972fb..564020e7 100644 --- a/src/Entity/Bug.php +++ b/src/Entity/Bug.php @@ -6,7 +6,9 @@ use DateTimeInterface; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; +use Tienvx\Bundle\MbtBundle\Entity\Bug\Video; use Tienvx\Bundle\MbtBundle\Model\Bug as BugModel; +use Tienvx\Bundle\MbtBundle\Model\Bug\VideoInterface; use Tienvx\Bundle\MbtBundle\Model\ProgressInterface; use Tienvx\Bundle\MbtBundle\Model\TaskInterface; use Tienvx\Bundle\MbtBundle\Repository\BugRepository; @@ -61,9 +63,9 @@ class Bug extends BugModel protected bool $closed = false; /** - * @ORM\Column(type="boolean") + * @ORM\Embedded(class="\Tienvx\Bundle\MbtBundle\Entity\Bug\Video") */ - protected bool $recording = false; + protected VideoInterface $video; /** * @ORM\Column(name="created_at", type="datetime") @@ -79,6 +81,7 @@ public function __construct() { parent::__construct(); $this->progress = new Progress(); + $this->video = new Video(); } /** diff --git a/src/Entity/Bug/Video.php b/src/Entity/Bug/Video.php new file mode 100644 index 00000000..cfae14bd --- /dev/null +++ b/src/Entity/Bug/Video.php @@ -0,0 +1,23 @@ +progress = new Progress(); + $this->video = new Video(); } public function setId(int $id) @@ -101,14 +104,14 @@ public function setClosed(bool $closed): void $this->closed = $closed; } - public function isRecording(): bool + public function getVideo(): VideoInterface { - return $this->recording; + return $this->video; } - public function setRecording(bool $recording): void + public function setVideo(VideoInterface $video): void { - $this->recording = $recording; + $this->video = $video; } public function setCreatedAt(DateTimeInterface $createdAt): void diff --git a/src/Model/Bug/Video.php b/src/Model/Bug/Video.php new file mode 100644 index 00000000..46a0c9fd --- /dev/null +++ b/src/Model/Bug/Video.php @@ -0,0 +1,29 @@ +recording; + } + + public function setRecording(bool $recording): void + { + $this->recording = $recording; + } + + public function getErrorMessage(): ?string + { + return $this->errorMessage; + } + + public function setErrorMessage(?string $errorMessage = null): void + { + $this->errorMessage = $errorMessage; + } +} diff --git a/src/Model/Bug/VideoInterface.php b/src/Model/Bug/VideoInterface.php new file mode 100644 index 00000000..48c1a75d --- /dev/null +++ b/src/Model/Bug/VideoInterface.php @@ -0,0 +1,14 @@ +setRecording(true); + $bug->getVideo()->setRecording(true); $this->getEntityManager()->flush(); } public function stopRecording(BugInterface $bug): void { - $bug->setRecording(false); + $bug->getVideo()->setRecording(false); // Recording bug may take long time. Reconnect to flush changes. $this->getEntityManager()->getConnection()->connect(); $this->getEntityManager()->flush(); } + + public function updateVideoErrorMessage(BugInterface $bug, string $videoErrorMessage): void + { + $bug->getVideo()->setErrorMessage($videoErrorMessage); + $this->getEntityManager()->flush(); + } } diff --git a/src/Repository/BugRepositoryInterface.php b/src/Repository/BugRepositoryInterface.php index f834c7c8..5301afda 100644 --- a/src/Repository/BugRepositoryInterface.php +++ b/src/Repository/BugRepositoryInterface.php @@ -16,4 +16,6 @@ public function increaseTotal(BugInterface $bug, int $total): void; public function startRecording(BugInterface $bug): void; public function stopRecording(BugInterface $bug): void; + + public function updateVideoErrorMessage(BugInterface $bug, string $videoErrorMessage): void; } diff --git a/src/Service/Bug/BugHelper.php b/src/Service/Bug/BugHelper.php index b8aa9e28..518cca27 100644 --- a/src/Service/Bug/BugHelper.php +++ b/src/Service/Bug/BugHelper.php @@ -4,6 +4,7 @@ use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException; use Symfony\Component\Messenger\MessageBusInterface; +use Throwable; use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Exception\ExceptionInterface; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; @@ -87,14 +88,16 @@ public function recordVideo(int $bugId): void { $bug = $this->getBug($bugId, 'record video for bug'); - if ($bug->isRecording()) { + if ($bug->getVideo()->isRecording()) { throw new RecoverableMessageHandlingException( sprintf('Can not record video for bug %d: bug is recording. Will retry later', $bug->getId()) ); } $this->bugRepository->startRecording($bug); - $this->stepsRunner->run($bug->getSteps(), $bug, true); + $this->stepsRunner->run($bug->getSteps(), $bug, true, function (Throwable $throwable) use ($bug): void { + $this->bugRepository->updateVideoErrorMessage($bug, $throwable->getMessage()); + }); $this->bugRepository->stopRecording($bug); } diff --git a/tests/Entity/Bug/VideoTest.php b/tests/Entity/Bug/VideoTest.php new file mode 100644 index 00000000..5847beb8 --- /dev/null +++ b/tests/Entity/Bug/VideoTest.php @@ -0,0 +1,20 @@ +video = $this->createVideo(); + } + + public function testRecording(): void + { + $this->assertFalse($this->video->isRecording()); + $this->video->setRecording(true); + $this->assertTrue($this->video->isRecording()); + } + + public function testErrorMessage(): void + { + $this->assertNull($this->video->getErrorMessage()); + $this->video->setErrorMessage('Something wrong'); + $this->assertSame('Something wrong', $this->video->getErrorMessage()); + } + + protected function createVideo(): VideoInterface + { + return new Video(); + } +} diff --git a/tests/Model/BugTest.php b/tests/Model/BugTest.php index 8b712a2c..21b03632 100644 --- a/tests/Model/BugTest.php +++ b/tests/Model/BugTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\TestCase; use SingleColorPetrinet\Model\Color; +use Tienvx\Bundle\MbtBundle\Entity\Bug\Video; use Tienvx\Bundle\MbtBundle\Entity\Progress; use Tienvx\Bundle\MbtBundle\Entity\Task; use Tienvx\Bundle\MbtBundle\Model\Bug; @@ -28,6 +29,7 @@ class BugTest extends TestCase protected array $steps; protected TaskInterface $task; protected ProgressInterface $progress; + protected Bug\VideoInterface $video; protected function setUp(): void { @@ -37,6 +39,7 @@ protected function setUp(): void ]; $this->task = new Task(); $this->progress = new Progress(); + $this->video = new Video(); $this->bug = $this->createBug(); $this->bug->setId(123); $this->bug->setTitle('bug title'); @@ -44,8 +47,8 @@ protected function setUp(): void $this->bug->setTask($this->task); $this->bug->setMessage('bug message'); $this->bug->setProgress($this->progress); + $this->bug->setVideo($this->video); $this->bug->setClosed(true); - $this->bug->setRecording(true); } public function testProperties(): void @@ -57,7 +60,7 @@ public function testProperties(): void $this->assertSame('bug message', $this->bug->getMessage()); $this->assertSame($this->progress, $this->bug->getProgress()); $this->assertSame(true, $this->bug->isClosed()); - $this->assertSame(true, $this->bug->isRecording()); + $this->assertSame($this->video, $this->bug->getVideo()); } protected function createBug(): BugInterface diff --git a/tests/Repository/BugRepositoryTest.php b/tests/Repository/BugRepositoryTest.php index 21d97f30..f502ce96 100644 --- a/tests/Repository/BugRepositoryTest.php +++ b/tests/Repository/BugRepositoryTest.php @@ -21,6 +21,7 @@ * @uses \Tienvx\Bundle\MbtBundle\Entity\Bug * @uses \Tienvx\Bundle\MbtBundle\Model\Bug * @uses \Tienvx\Bundle\MbtBundle\Model\Progress + * @uses \Tienvx\Bundle\MbtBundle\Model\Bug\Video */ class BugRepositoryTest extends TestCase { @@ -154,20 +155,28 @@ public function testIncreaseTotal(): void public function testStartRecordingBug(): void { - $this->bug->setRecording(false); + $this->bug->getVideo()->setRecording(false); $this->manager->expects($this->once())->method('flush'); $this->bugRepository->startRecording($this->bug); - $this->assertTrue($this->bug->isRecording()); + $this->assertTrue($this->bug->getVideo()->isRecording()); } public function testStopRecordingBug(): void { $connection = $this->createMock(Connection::class); $connection->expects($this->once())->method('connect'); - $this->bug->setRecording(true); + $this->bug->getVideo()->setRecording(true); $this->manager->expects($this->once())->method('flush'); $this->manager->expects($this->once())->method('getConnection')->willReturn($connection); $this->bugRepository->stopRecording($this->bug); - $this->assertFalse($this->bug->isRecording()); + $this->assertFalse($this->bug->getVideo()->isRecording()); + } + + public function testUpdateVideoErrorMessage(): void + { + $this->assertNull($this->bug->getVideo()->getErrorMessage()); + $this->manager->expects($this->once())->method('flush'); + $this->bugRepository->updateVideoErrorMessage($this->bug, 'Something wrong'); + $this->assertSame('Something wrong', $this->bug->getVideo()->getErrorMessage()); } } diff --git a/tests/Service/Bug/BugHelperTest.php b/tests/Service/Bug/BugHelperTest.php index 9af48b9e..ab6ee3b7 100644 --- a/tests/Service/Bug/BugHelperTest.php +++ b/tests/Service/Bug/BugHelperTest.php @@ -2,10 +2,12 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service\Bug; +use Exception; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException; use Symfony\Component\Messenger\MessageBusInterface; +use Throwable; use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Entity\Progress; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; @@ -32,6 +34,7 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Bug * @uses \Tienvx\Bundle\MbtBundle\Model\Task * @uses \Tienvx\Bundle\MbtBundle\Model\Progress + * @uses \Tienvx\Bundle\MbtBundle\Model\Bug\Video * @uses \Tienvx\Bundle\MbtBundle\Message\ReportBugMessage * @uses \Tienvx\Bundle\MbtBundle\Message\RecordVideoMessage */ @@ -237,20 +240,50 @@ public function testRunTaskAlreadyRunning(): void { $this->expectException(RecoverableMessageHandlingException::class); $this->expectExceptionMessage('Can not record video for bug 123: bug is recording. Will retry later'); - $this->bug->setRecording(true); + $this->bug->getVideo()->setRecording(true); $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn($this->bug); $this->helper->recordVideo(123); } - public function testRecordVideo(): void + /** + * @dataProvider exceptionProvider + */ + public function testRecordVideo(?Throwable $exception, bool $updateVideoErrorMessage): void { $this->stepsRunner ->expects($this->once()) ->method('run') - ->with($this->bug->getSteps(), $this->bug, true); + ->with( + $this->bug->getSteps(), + $this->bug, + true, + $this->callback(function (callable $exceptionCallback) use ($exception) { + if ($exception) { + $exceptionCallback($exception); + } + + return true; + }) + ); + if ($exception && $updateVideoErrorMessage) { + $this->bugRepository + ->expects($this->once()) + ->method('updateVideoErrorMessage') + ->with($this->bug, $exception->getMessage()); + } else { + $this->bugRepository->expects($this->never())->method('updateVideoErrorMessage'); + } $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn($this->bug); $this->bugRepository->expects($this->once())->method('startRecording')->with($this->bug); $this->bugRepository->expects($this->once())->method('stopRecording')->with($this->bug); $this->helper->recordVideo(123); } + + public function exceptionProvider(): array + { + return [ + [null, false], + [new Exception('Something wrong'), true], + ]; + } } From 9c0b8fbeeed377282ea267ac54be45ff76f87a9c Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 12 Apr 2022 23:36:00 +0700 Subject: [PATCH 31/85] More tests for CommandPreprocessor --- tests/Command/CommandPreprocessorTest.php | 114 ++++++++++++++++------ 1 file changed, 86 insertions(+), 28 deletions(-) diff --git a/tests/Command/CommandPreprocessorTest.php b/tests/Command/CommandPreprocessorTest.php index ea43b306..41623619 100644 --- a/tests/Command/CommandPreprocessorTest.php +++ b/tests/Command/CommandPreprocessorTest.php @@ -5,8 +5,11 @@ use PHPUnit\Framework\TestCase; use SingleColorPetrinet\Model\Color; use Tienvx\Bundle\MbtBundle\Command\CommandPreprocessor; -use Tienvx\Bundle\MbtBundle\Command\Runner\AssertionRunner; +use Tienvx\Bundle\MbtBundle\Command\Runner\KeyboardCommandRunner; use Tienvx\Bundle\MbtBundle\Command\Runner\MouseCommandRunner; +use Tienvx\Bundle\MbtBundle\Command\Runner\ScriptCommandRunner; +use Tienvx\Bundle\MbtBundle\Command\Runner\StoreCommandRunner; +use Tienvx\Bundle\MbtBundle\Command\Runner\WindowCommandRunner; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\Command; /** @@ -16,39 +19,94 @@ */ class CommandPreprocessorTest extends TestCase { - public function testProcess(): void - { + /** + * @dataProvider commandProvider + */ + public function testProcess( + string $commandString, + ?string $target, + ?string $value, + array $values, + ?string $newTarget, + ?string $newValue + ): void { $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT); - $command->setTarget('${variable}'); - $command->setValue('value'); + $command->setCommand($commandString); + $command->setTarget($target); + $command->setValue($value); $color = new Color(); - $color->setValues([ - 'variable' => 'key', - 'key' => 'value', - ]); + $color->setValues($values); $preprocessor = new CommandPreprocessor(); $newCommand = $preprocessor->process($command, $color); - $this->assertSame(AssertionRunner::ASSERT, $newCommand->getCommand()); - $this->assertSame('key', $newCommand->getTarget()); - $this->assertSame('value', $newCommand->getValue()); + $this->assertSame($commandString, $newCommand->getCommand()); + $this->assertSame($newTarget, $newCommand->getTarget()); + $this->assertSame($newValue, $newCommand->getValue()); } - public function testProcessEmpty(): void + public function commandProvider(): array { - $command = new Command(); - $command->setCommand(MouseCommandRunner::CLICK); - $command->setTarget(''); - $command->setValue(null); - $color = new Color(); - $color->setValues([ - 'variable' => 'key', - 'key' => 'value', - ]); - $preprocessor = new CommandPreprocessor(); - $newCommand = $preprocessor->process($command, $color); - $this->assertSame(MouseCommandRunner::CLICK, $newCommand->getCommand()); - $this->assertSame('', $newCommand->getTarget()); - $this->assertSame(null, $newCommand->getValue()); + return [ + [ + KeyboardCommandRunner::TYPE, + 'xpath=//div[text()=\'${variable1}\']/input', + '${variable2}', + [ + 'variable1' => 'value1', + 'variable2' => 'value2', + ], + 'xpath=//div[text()=\'value1\']/input', + 'value2', + ], + [ + ScriptCommandRunner::EXECUTE_SCRIPT, + 'return ${variable1} + 1', + 'variable1', + [ + 'variable1' => 123, + ], + 'return 123 + 1', + 'variable1', + ], + [ + StoreCommandRunner::STORE, + 'closed', + '${key}', + [ + 'key' => 'status', + ], + 'closed', + 'status', + ], + [ + WindowCommandRunner::OPEN, + 'http://example.com/${path}', + null, + [], + 'http://example.com/path', + null, + ], + [ + MouseCommandRunner::CLICK, + 'css=.id', + null, + [ + 'key1' => 'value1', + 'key2' => 'value2', + ], + 'css=.id', + null, + ], + [ + 'invalid', + '', + null, + [ + 'key1' => 'value1', + 'key2' => 'value2', + ], + '', + null, + ], + ]; } } From 418709e939e23d7df139f9b59001f0aeb6ae07e0 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 16 Apr 2022 14:35:02 +0700 Subject: [PATCH 32/85] Use last step color when running bug steps --- src/Model/Bug.php | 14 +- src/Model/BugInterface.php | 4 +- src/Model/Debug.php | 18 + src/Model/DebugInterface.php | 16 + src/Model/Task.php | 18 +- src/Model/TaskInterface.php | 6 +- src/Reducer/HandlerTemplate.php | 11 +- src/Repository/BugRepository.php | 6 - src/Repository/BugRepositoryInterface.php | 2 - src/Resources/config/services.php | 16 +- src/Service/Bug/BugHelper.php | 22 +- src/Service/Bug/BugHelperInterface.php | 4 - src/Service/ExpressionLanguage.php | 2 +- src/Service/SelenoidHelper.php | 50 +-- src/Service/SelenoidHelperInterface.php | 16 +- .../Builder}/ShortestPathStepsBuilder.php | 2 +- .../Builder}/StepsBuilderInterface.php | 2 +- src/Service/Step/Runner/BugStepsRunner.php | 42 +++ src/Service/{ => Step/Runner}/StepRunner.php | 2 +- .../{ => Step/Runner}/StepRunnerInterface.php | 2 +- src/Service/Step/Runner/StepsRunner.php | 75 ++++ .../Step/Runner/StepsRunnerInterface.php | 10 + src/Service/Step/Runner/TaskStepsRunner.php | 66 ++++ src/Service/StepsRunner.php | 72 ---- src/Service/StepsRunnerInterface.php | 17 - src/Service/Task/TaskHelper.php | 28 +- tests/Entity/TaskTest.php | 1 + tests/Model/BugTest.php | 10 + tests/Model/TaskTest.php | 11 + tests/Reducer/DispatcherTestCase.php | 2 +- tests/Reducer/HandlerTestCase.php | 13 +- tests/Reducer/Random/RandomHandlerTest.php | 1 + tests/Reducer/Split/SplitHandlerTest.php | 1 + tests/Repository/BugRepositoryTest.php | 8 - tests/Service/Bug/BugHelperTest.php | 43 +-- tests/Service/SelenoidHelperTest.php | 80 ++--- .../Builder}/ShortestPathStepsBuilderTest.php | 6 +- .../Step/Runner/BugStepsRunnerTest.php | 35 ++ .../{ => Step/Runner}/StepRunnerTest.php | 6 +- .../Step/Runner/StepsRunnerTestCase.php | 160 +++++++++ .../Step/Runner/TaskStepsRunnerTest.php | 76 ++++ tests/Service/StepsRunnerTest.php | 326 ------------------ tests/Service/Task/TaskHelperTest.php | 65 +--- 43 files changed, 688 insertions(+), 679 deletions(-) create mode 100644 src/Model/Debug.php create mode 100644 src/Model/DebugInterface.php rename src/Service/{ => Step/Builder}/ShortestPathStepsBuilder.php (97%) rename src/Service/{ => Step/Builder}/StepsBuilderInterface.php (76%) create mode 100644 src/Service/Step/Runner/BugStepsRunner.php rename src/Service/{ => Step/Runner}/StepRunner.php (96%) rename src/Service/{ => Step/Runner}/StepRunnerInterface.php (84%) create mode 100644 src/Service/Step/Runner/StepsRunner.php create mode 100644 src/Service/Step/Runner/StepsRunnerInterface.php create mode 100644 src/Service/Step/Runner/TaskStepsRunner.php delete mode 100644 src/Service/StepsRunner.php delete mode 100644 src/Service/StepsRunnerInterface.php rename tests/Service/{ => Step/Builder}/ShortestPathStepsBuilderTest.php (98%) create mode 100644 tests/Service/Step/Runner/BugStepsRunnerTest.php rename tests/Service/{ => Step/Runner}/StepRunnerTest.php (94%) create mode 100644 tests/Service/Step/Runner/StepsRunnerTestCase.php create mode 100644 tests/Service/Step/Runner/TaskStepsRunnerTest.php delete mode 100644 tests/Service/StepsRunnerTest.php diff --git a/src/Model/Bug.php b/src/Model/Bug.php index 14811538..c57e13c9 100644 --- a/src/Model/Bug.php +++ b/src/Model/Bug.php @@ -7,8 +7,10 @@ use Tienvx\Bundle\MbtBundle\Model\Bug\Video; use Tienvx\Bundle\MbtBundle\Model\Bug\VideoInterface; -class Bug implements BugInterface +class Bug extends Debug implements BugInterface { + protected const BUG = 'bug'; + protected ?int $id = null; protected string $title; protected array $steps = []; @@ -114,6 +116,16 @@ public function setVideo(VideoInterface $video): void $this->video = $video; } + public function getLogName(): string + { + return sprintf('%s-%d.log', static::BUG, $this->getId()); + } + + public function getVideoName(): string + { + return sprintf('%s-%d.mp4', static::BUG, $this->getId()); + } + public function setCreatedAt(DateTimeInterface $createdAt): void { $this->createdAt = $createdAt; diff --git a/src/Model/BugInterface.php b/src/Model/BugInterface.php index 5965155c..905bb95e 100644 --- a/src/Model/BugInterface.php +++ b/src/Model/BugInterface.php @@ -6,7 +6,7 @@ use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; use Tienvx\Bundle\MbtBundle\Model\Bug\VideoInterface; -interface BugInterface +interface BugInterface extends DebugInterface { public function setId(int $id); @@ -25,8 +25,6 @@ public function setSteps(array $steps): void; public function addStep(StepInterface $step): void; - public function getTask(): TaskInterface; - public function setTask(TaskInterface $task): void; public function getMessage(): string; diff --git a/src/Model/Debug.php b/src/Model/Debug.php new file mode 100644 index 00000000..1dcfa546 --- /dev/null +++ b/src/Model/Debug.php @@ -0,0 +1,18 @@ +debug; + } + + public function setDebug(bool $debug): void + { + $this->debug = $debug; + } +} diff --git a/src/Model/DebugInterface.php b/src/Model/DebugInterface.php new file mode 100644 index 00000000..b8518acc --- /dev/null +++ b/src/Model/DebugInterface.php @@ -0,0 +1,16 @@ +getId()); + } + + public function getVideoName(): string { - return $this->debug; + return sprintf('%s-%d.mp4', static::TASK, $this->getId()); } - public function setDebug(bool $debug): void + public function getTask(): TaskInterface { - $this->debug = $debug; + return $this; } public function setCreatedAt(DateTimeInterface $createdAt): void diff --git a/src/Model/TaskInterface.php b/src/Model/TaskInterface.php index 06c2a59a..145f799f 100644 --- a/src/Model/TaskInterface.php +++ b/src/Model/TaskInterface.php @@ -7,7 +7,7 @@ use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; use Tienvx\Bundle\MbtBundle\Model\Task\BrowserInterface; -interface TaskInterface +interface TaskInterface extends DebugInterface { public function setId(int $id); @@ -37,10 +37,6 @@ public function getBugs(): Collection; public function addBug(BugInterface $bug): void; - public function isDebug(): bool; - - public function setDebug(bool $debug): void; - public function setCreatedAt(DateTimeInterface $createdAt): void; public function getCreatedAt(): DateTimeInterface; diff --git a/src/Reducer/HandlerTemplate.php b/src/Reducer/HandlerTemplate.php index 264ab79e..8b8bb30b 100644 --- a/src/Reducer/HandlerTemplate.php +++ b/src/Reducer/HandlerTemplate.php @@ -7,20 +7,20 @@ use Tienvx\Bundle\MbtBundle\Message\ReduceBugMessage; use Tienvx\Bundle\MbtBundle\Model\BugInterface; use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; -use Tienvx\Bundle\MbtBundle\Service\StepsBuilderInterface; -use Tienvx\Bundle\MbtBundle\Service\StepsRunnerInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Builder\StepsBuilderInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; abstract class HandlerTemplate implements HandlerInterface { protected BugRepositoryInterface $bugRepository; protected MessageBusInterface $messageBus; - protected StepsRunnerInterface $stepsRunner; + protected BugStepsRunner $stepsRunner; protected StepsBuilderInterface $stepsBuilder; public function __construct( BugRepositoryInterface $bugRepository, MessageBusInterface $messageBus, - StepsRunnerInterface $stepsRunner, + BugStepsRunner $stepsRunner, StepsBuilderInterface $stepsBuilder ) { $this->bugRepository = $bugRepository; @@ -36,7 +36,8 @@ public function handle(BugInterface $bug, int $from, int $to): void return; } - $this->stepsRunner->run($newSteps, $bug, false, function (Throwable $throwable) use ($bug, $newSteps): void { + $bug->setDebug(false); + $this->stepsRunner->run($newSteps, $bug, function (Throwable $throwable) use ($bug, $newSteps): void { if ($throwable->getMessage() === $bug->getMessage()) { $this->bugRepository->updateSteps($bug, $newSteps); $this->messageBus->dispatch(new ReduceBugMessage($bug->getId())); diff --git a/src/Repository/BugRepository.php b/src/Repository/BugRepository.php index e27b57d9..4acfb889 100644 --- a/src/Repository/BugRepository.php +++ b/src/Repository/BugRepository.php @@ -65,10 +65,4 @@ public function stopRecording(BugInterface $bug): void $this->getEntityManager()->getConnection()->connect(); $this->getEntityManager()->flush(); } - - public function updateVideoErrorMessage(BugInterface $bug, string $videoErrorMessage): void - { - $bug->getVideo()->setErrorMessage($videoErrorMessage); - $this->getEntityManager()->flush(); - } } diff --git a/src/Repository/BugRepositoryInterface.php b/src/Repository/BugRepositoryInterface.php index 5301afda..f834c7c8 100644 --- a/src/Repository/BugRepositoryInterface.php +++ b/src/Repository/BugRepositoryInterface.php @@ -16,6 +16,4 @@ public function increaseTotal(BugInterface $bug, int $total): void; public function startRecording(BugInterface $bug): void; public function stopRecording(BugInterface $bug): void; - - public function updateVideoErrorMessage(BugInterface $bug, string $videoErrorMessage): void; } diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index f0003697..e6d0ca1f 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -62,12 +62,13 @@ use Tienvx\Bundle\MbtBundle\Service\Petrinet\PetrinetHelperInterface; use Tienvx\Bundle\MbtBundle\Service\SelenoidHelper; use Tienvx\Bundle\MbtBundle\Service\SelenoidHelperInterface; -use Tienvx\Bundle\MbtBundle\Service\ShortestPathStepsBuilder; -use Tienvx\Bundle\MbtBundle\Service\StepRunner; -use Tienvx\Bundle\MbtBundle\Service\StepRunnerInterface; -use Tienvx\Bundle\MbtBundle\Service\StepsBuilderInterface; -use Tienvx\Bundle\MbtBundle\Service\StepsRunner; -use Tienvx\Bundle\MbtBundle\Service\StepsRunnerInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Builder\ShortestPathStepsBuilder; +use Tienvx\Bundle\MbtBundle\Service\Step\Builder\StepsBuilderInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepRunnerInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepsRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepsRunnerInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\TaskStepsRunner; use Tienvx\Bundle\MbtBundle\Service\Task\TaskHelper; use Tienvx\Bundle\MbtBundle\Service\Task\TaskHelperInterface; use Tienvx\Bundle\MbtBundle\Validator\TagsValidator; @@ -239,8 +240,7 @@ ->args([ service(GeneratorManagerInterface::class), service(TaskRepositoryInterface::class), - service(StepsRunnerInterface::class), - service(BugHelperInterface::class), + service(TaskStepsRunner::class), service(ConfigInterface::class), ]) ->alias(TaskHelperInterface::class, TaskHelper::class) diff --git a/src/Service/Bug/BugHelper.php b/src/Service/Bug/BugHelper.php index 518cca27..42f60a00 100644 --- a/src/Service/Bug/BugHelper.php +++ b/src/Service/Bug/BugHelper.php @@ -5,7 +5,6 @@ use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException; use Symfony\Component\Messenger\MessageBusInterface; use Throwable; -use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Exception\ExceptionInterface; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; use Tienvx\Bundle\MbtBundle\Message\RecordVideoMessage; @@ -14,7 +13,7 @@ use Tienvx\Bundle\MbtBundle\Reducer\ReducerManagerInterface; use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; -use Tienvx\Bundle\MbtBundle\Service\StepsRunnerInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; class BugHelper implements BugHelperInterface { @@ -22,7 +21,7 @@ class BugHelper implements BugHelperInterface protected BugRepositoryInterface $bugRepository; protected MessageBusInterface $messageBus; protected BugNotifierInterface $bugNotifier; - protected StepsRunnerInterface $stepsRunner; + protected BugStepsRunner $stepsRunner; protected ConfigInterface $config; public function __construct( @@ -30,7 +29,7 @@ public function __construct( BugRepositoryInterface $bugRepository, MessageBusInterface $messageBus, BugNotifierInterface $bugNotifier, - StepsRunnerInterface $stepsRunner, + BugStepsRunner $stepsRunner, ConfigInterface $config ) { $this->reducerManager = $reducerManager; @@ -95,22 +94,13 @@ public function recordVideo(int $bugId): void } $this->bugRepository->startRecording($bug); - $this->stepsRunner->run($bug->getSteps(), $bug, true, function (Throwable $throwable) use ($bug): void { - $this->bugRepository->updateVideoErrorMessage($bug, $throwable->getMessage()); + $bug->setDebug(true); + $this->stepsRunner->run($bug->getSteps(), $bug, function (Throwable $throwable) use ($bug) { + $bug->getVideo()->setErrorMessage($throwable->getMessage()); }); $this->bugRepository->stopRecording($bug); } - public function createBug(array $steps, string $message): BugInterface - { - $bug = new Bug(); - $bug->setTitle(''); - $bug->setSteps($steps); - $bug->setMessage($message); - - return $bug; - } - protected function getBug(int $bugId, string $action): BugInterface { $bug = $this->bugRepository->find($bugId); diff --git a/src/Service/Bug/BugHelperInterface.php b/src/Service/Bug/BugHelperInterface.php index 274ce1ad..a979560d 100644 --- a/src/Service/Bug/BugHelperInterface.php +++ b/src/Service/Bug/BugHelperInterface.php @@ -2,8 +2,6 @@ namespace Tienvx\Bundle\MbtBundle\Service\Bug; -use Tienvx\Bundle\MbtBundle\Model\BugInterface; - interface BugHelperInterface { public function reduceBug(int $bugId): void; @@ -13,6 +11,4 @@ public function reduceSteps(int $bugId, int $length, int $from, int $to): void; public function reportBug(int $bugId): void; public function recordVideo(int $bugId): void; - - public function createBug(array $steps, string $message): BugInterface; } diff --git a/src/Service/ExpressionLanguage.php b/src/Service/ExpressionLanguage.php index ac70a94e..e4c67881 100644 --- a/src/Service/ExpressionLanguage.php +++ b/src/Service/ExpressionLanguage.php @@ -8,6 +8,6 @@ class ExpressionLanguage extends BaseExpressionLanguage { protected function registerFunctions() { - // Don't register const function + // Don't register constant function } } diff --git a/src/Service/SelenoidHelper.php b/src/Service/SelenoidHelper.php index ad22ea39..457b0ce1 100644 --- a/src/Service/SelenoidHelper.php +++ b/src/Service/SelenoidHelper.php @@ -5,13 +5,11 @@ use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\WebDriverCapabilityType; -use Tienvx\Bundle\MbtBundle\Model\BugInterface; -use Tienvx\Bundle\MbtBundle\Model\TaskInterface; +use Tienvx\Bundle\MbtBundle\Model\DebugInterface; class SelenoidHelper implements SelenoidHelperInterface { - private const BUG = 'bug'; - private const TASK = 'task'; + protected const WAIT_SECONDS = 2; protected string $webdriverUri; @@ -20,38 +18,42 @@ public function setWebdriverUri(string $webdriverUri): void $this->webdriverUri = $webdriverUri; } - public function getVideoUrl(TaskInterface|BugInterface $entity): string + public function getVideoUrl(DebugInterface $entity): string { - return sprintf('%s/video/%s', $this->webdriverUri, $this->getVideoName($entity)); + return sprintf('%s/video/%s', $this->webdriverUri, $entity->getVideoName()); } - public function getLogUrl(TaskInterface|BugInterface $entity): string + public function getLogUrl(DebugInterface $entity): string { - return sprintf('%s/logs/%s', $this->webdriverUri, $this->getLogName($entity)); + return sprintf('%s/logs/%s', $this->webdriverUri, $entity->getLogName()); } - public function createDriver(DesiredCapabilities $capabilities): RemoteWebDriver + public function createDriver(DebugInterface $entity): RemoteWebDriver { - return RemoteWebDriver::create( + $driver = $this->createDriverInternal( $this->webdriverUri . '/wd/hub', - $capabilities + $this->getCapabilities($entity) ); + if ($entity->isDebug()) { + $this->waitForVideoContainer(); + } + + return $driver; } - public function getCapabilities(TaskInterface|BugInterface $entity, bool $debug = false): DesiredCapabilities + protected function getCapabilities(DebugInterface $entity): DesiredCapabilities { - $task = $entity instanceof BugInterface ? $entity->getTask() : $entity; $caps = [ - WebDriverCapabilityType::BROWSER_NAME => $task->getBrowser()->getName(), - WebDriverCapabilityType::VERSION => $task->getBrowser()->getVersion(), + WebDriverCapabilityType::BROWSER_NAME => $entity->getTask()->getBrowser()->getName(), + WebDriverCapabilityType::VERSION => $entity->getTask()->getBrowser()->getVersion(), 'enableVNC' => false, - 'enableLog' => $debug, - 'enableVideo' => $debug, + 'enableLog' => $entity->isDebug(), + 'enableVideo' => $entity->isDebug(), ]; - if ($debug) { + if ($entity->isDebug()) { $caps += [ - 'logName' => $this->getLogName($entity), - 'videoName' => $this->getVideoName($entity), + 'logName' => $entity->getLogName(), + 'videoName' => $entity->getVideoName(), 'videoFrameRate' => 60, ]; } @@ -59,13 +61,13 @@ public function getCapabilities(TaskInterface|BugInterface $entity, bool $debug return new DesiredCapabilities($caps); } - public function getVideoName(TaskInterface|BugInterface $entity): string + protected function waitForVideoContainer(): void { - return sprintf('%s-%d.mp4', $entity instanceof TaskInterface ? static::TASK : static::BUG, $entity->getId()); + sleep(static::WAIT_SECONDS); } - public function getLogName(TaskInterface|BugInterface $entity): string + protected function createDriverInternal($serverUrl, DesiredCapabilities $capabilities): RemoteWebDriver { - return sprintf('%s-%d.log', $entity instanceof TaskInterface ? static::TASK : static::BUG, $entity->getId()); + return RemoteWebDriver::create($serverUrl, $capabilities); } } diff --git a/src/Service/SelenoidHelperInterface.php b/src/Service/SelenoidHelperInterface.php index 58955dc2..93c7152d 100644 --- a/src/Service/SelenoidHelperInterface.php +++ b/src/Service/SelenoidHelperInterface.php @@ -2,24 +2,16 @@ namespace Tienvx\Bundle\MbtBundle\Service; -use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; -use Tienvx\Bundle\MbtBundle\Model\BugInterface; -use Tienvx\Bundle\MbtBundle\Model\TaskInterface; +use Tienvx\Bundle\MbtBundle\Model\DebugInterface; interface SelenoidHelperInterface { public function setWebdriverUri(string $webdriverUri): void; - public function getVideoUrl(TaskInterface|BugInterface $entity): string; + public function getVideoUrl(DebugInterface $entity): string; - public function getLogUrl(TaskInterface|BugInterface $entity): string; + public function getLogUrl(DebugInterface $entity): string; - public function createDriver(DesiredCapabilities $capabilities): RemoteWebDriver; - - public function getCapabilities(TaskInterface|BugInterface $entity, bool $debug = false): DesiredCapabilities; - - public function getVideoName(TaskInterface|BugInterface $entity): string; - - public function getLogName(TaskInterface|BugInterface $entity): string; + public function createDriver(DebugInterface $entity): RemoteWebDriver; } diff --git a/src/Service/ShortestPathStepsBuilder.php b/src/Service/Step/Builder/ShortestPathStepsBuilder.php similarity index 97% rename from src/Service/ShortestPathStepsBuilder.php rename to src/Service/Step/Builder/ShortestPathStepsBuilder.php index 284beb58..6c35355a 100644 --- a/src/Service/ShortestPathStepsBuilder.php +++ b/src/Service/Step/Builder/ShortestPathStepsBuilder.php @@ -1,6 +1,6 @@ lastColor = new Color(); + + return parent::start($entity); + } + + protected function stop(?RemoteWebDriver $driver): void + { + parent::stop($driver); + $this->lastColor = null; + } + + protected function runStep(StepInterface $step, RevisionInterface $revision, RemoteWebDriver $driver): void + { + $color = clone $step->getColor(); + $step->setColor($this->lastColor); + parent::runStep($step, $revision, $driver); + $this->lastColor = $color; + } + + protected function catchException(callable $handleException, Throwable $throwable, ?StepInterface $step): void + { + $handleException($throwable); + } +} diff --git a/src/Service/StepRunner.php b/src/Service/Step/Runner/StepRunner.php similarity index 96% rename from src/Service/StepRunner.php rename to src/Service/Step/Runner/StepRunner.php index 616968ac..14a34186 100644 --- a/src/Service/StepRunner.php +++ b/src/Service/Step/Runner/StepRunner.php @@ -1,6 +1,6 @@ selenoidHelper = $selenoidHelper; + $this->stepRunner = $stepRunner; + } + + /** + * @throws ExceptionInterface + */ + public function run(iterable $steps, DebugInterface $entity, callable $handleException): void + { + try { + $driver = $this->start($entity); + foreach ($steps as $step) { + if (!$step instanceof StepInterface) { + throw new UnexpectedValueException(sprintf('Step must be instance of "%s".', StepInterface::class)); + } + $this->runStep($step, $entity->getTask()->getModelRevision(), $driver); + if ($this->canStop($step)) { + break; + } + } + } catch (ExceptionInterface $exception) { + throw $exception; + } catch (Throwable $throwable) { + $this->catchException($handleException, $throwable, $step ?? null); + } finally { + $this->stop($driver ?? null); + } + } + + protected function start(DebugInterface $entity): RemoteWebDriver + { + return $this->selenoidHelper->createDriver($entity); + } + + protected function runStep(StepInterface $step, RevisionInterface $revision, RemoteWebDriver $driver): void + { + $this->stepRunner->run($step, $revision, $driver); + } + + protected function canStop(StepInterface $step): bool + { + return false; + } + + abstract protected function catchException( + callable $handleException, + Throwable $throwable, + ?StepInterface $step + ): void; + + protected function stop(?RemoteWebDriver $driver): void + { + $driver?->quit(); + } +} diff --git a/src/Service/Step/Runner/StepsRunnerInterface.php b/src/Service/Step/Runner/StepsRunnerInterface.php new file mode 100644 index 00000000..18cc4ffb --- /dev/null +++ b/src/Service/Step/Runner/StepsRunnerInterface.php @@ -0,0 +1,10 @@ +config = $config; + } + + protected function start(DebugInterface $entity): RemoteWebDriver + { + $this->steps = []; + + return parent::start($entity); + } + + protected function catchException(callable $handleException, Throwable $throwable, ?StepInterface $step): void + { + if ($step instanceof StepInterface) { + // Last step cause the bug, we can't capture it. We capture it here. + $this->steps[] = clone $step; + } + $handleException($this->createBug($this->steps, $throwable->getMessage())); + } + + protected function stop(?RemoteWebDriver $driver): void + { + parent::stop($driver); + $this->steps = []; + } + + protected function canStop(StepInterface $step): bool + { + $this->steps[] = clone $step; + + return count($this->steps) >= $this->config->getMaxSteps(); + } + + protected function createBug(array $steps, string $message): BugInterface + { + $bug = new Bug(); + $bug->setTitle(''); + $bug->setSteps($steps); + $bug->setMessage($message); + + return $bug; + } +} diff --git a/src/Service/StepsRunner.php b/src/Service/StepsRunner.php deleted file mode 100644 index c3273172..00000000 --- a/src/Service/StepsRunner.php +++ /dev/null @@ -1,72 +0,0 @@ -selenoidHelper = $selenoidHelper; - $this->stepRunner = $stepRunner; - } - - /** - * @throws ExceptionInterface - */ - public function run( - iterable $steps, - TaskInterface|BugInterface $entity, - bool $debug = false, - ?callable $exceptionCallback = null, - ?callable $runCallback = null - ): void { - try { - $driver = $this->selenoidHelper->createDriver($this->selenoidHelper->getCapabilities($entity, $debug)); - if ($debug) { - $this->waitForVideoContainer(); - } - foreach ($steps as $step) { - if (!$step instanceof StepInterface) { - throw new UnexpectedValueException(sprintf('Step must be instance of "%s".', StepInterface::class)); - } - $this->stepRunner->run( - $step, - ($entity instanceof BugInterface ? $entity->getTask() : $entity)->getModelRevision(), - $driver - ); - if (is_callable($runCallback)) { - if ($runCallback($step)) { - break; - } - } - } - } catch (ExceptionInterface $exception) { - throw $exception; - } catch (Throwable $throwable) { - if (is_callable($exceptionCallback)) { - $exceptionCallback($throwable, $step ?? null); - } - } finally { - if (isset($driver)) { - $driver->quit(); - } - } - } - - protected function waitForVideoContainer(): void - { - sleep(static::WAIT_SECONDS); - } -} diff --git a/src/Service/StepsRunnerInterface.php b/src/Service/StepsRunnerInterface.php deleted file mode 100644 index 3d51a8e1..00000000 --- a/src/Service/StepsRunnerInterface.php +++ /dev/null @@ -1,17 +0,0 @@ -generatorManager = $generatorManager; $this->taskRepository = $taskRepository; $this->stepsRunner = $stepsRunner; - $this->bugHelper = $bugHelper; $this->config = $config; } @@ -51,22 +46,11 @@ public function run(int $taskId): void $this->taskRepository->startRunning($task); - $steps = []; $this->stepsRunner->run( $this->generatorManager->getGenerator($this->config->getGenerator())->generate($task), $task, - $task->isDebug(), - function (Throwable $throwable, ?StepInterface $step) use ($task, &$steps): void { - if ($step instanceof StepInterface) { - // Last step cause the bug, we can't capture it. We capture it here. - $steps[] = clone $step; - } - $task->addBug($this->bugHelper->createBug($steps, $throwable->getMessage())); - }, - function (StepInterface $step) use (&$steps): bool { - $steps[] = clone $step; - - return count($steps) >= $this->config->getMaxSteps(); + function (BugInterface $bug) use ($task) { + $task->addBug($bug); } ); diff --git a/tests/Entity/TaskTest.php b/tests/Entity/TaskTest.php index c215ccb7..99d68246 100644 --- a/tests/Entity/TaskTest.php +++ b/tests/Entity/TaskTest.php @@ -17,6 +17,7 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision * @uses \Tienvx\Bundle\MbtBundle\Entity\Task\Browser * @uses \Tienvx\Bundle\MbtBundle\Model\Task\Browser + * @uses \Tienvx\Bundle\MbtBundle\Model\Debug */ class TaskTest extends TaskModelTest { diff --git a/tests/Model/BugTest.php b/tests/Model/BugTest.php index 21b03632..70a472cf 100644 --- a/tests/Model/BugTest.php +++ b/tests/Model/BugTest.php @@ -63,6 +63,16 @@ public function testProperties(): void $this->assertSame($this->video, $this->bug->getVideo()); } + public function testGetLogName(): void + { + $this->assertSame('bug-123.log', $this->bug->getLogName()); + } + + public function testGetVideoName(): void + { + $this->assertSame('bug-123.mp4', $this->bug->getVideoName()); + } + protected function createBug(): BugInterface { return new Bug(); diff --git a/tests/Model/TaskTest.php b/tests/Model/TaskTest.php index c3aed8f7..02c4a34b 100644 --- a/tests/Model/TaskTest.php +++ b/tests/Model/TaskTest.php @@ -19,6 +19,7 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision * @uses \Tienvx\Bundle\MbtBundle\Entity\Task\Browser * @uses \Tienvx\Bundle\MbtBundle\Model\Task\Browser + * @uses \Tienvx\Bundle\MbtBundle\Model\Debug */ class TaskTest extends TestCase { @@ -57,6 +58,16 @@ public function testProperties(): void $this->assertSame(true, $this->task->isDebug()); } + public function testGetLogName(): void + { + $this->assertSame('task-123.log', $this->task->getLogName()); + } + + public function testGetVideoName(): void + { + $this->assertSame('task-123.mp4', $this->task->getVideoName()); + } + protected function createTask(): TaskInterface { return new Task(); diff --git a/tests/Reducer/DispatcherTestCase.php b/tests/Reducer/DispatcherTestCase.php index e6caa2ba..b0b6727e 100644 --- a/tests/Reducer/DispatcherTestCase.php +++ b/tests/Reducer/DispatcherTestCase.php @@ -12,7 +12,7 @@ use Tienvx\Bundle\MbtBundle\Model\BugInterface; use Tienvx\Bundle\MbtBundle\Reducer\DispatcherInterface; -class DispatcherTestCase extends TestCase +abstract class DispatcherTestCase extends TestCase { protected DispatcherInterface $dispatcher; protected MessageBusInterface $messageBus; diff --git a/tests/Reducer/HandlerTestCase.php b/tests/Reducer/HandlerTestCase.php index f601aa11..55788a25 100644 --- a/tests/Reducer/HandlerTestCase.php +++ b/tests/Reducer/HandlerTestCase.php @@ -14,15 +14,15 @@ use Tienvx\Bundle\MbtBundle\Model\BugInterface; use Tienvx\Bundle\MbtBundle\Reducer\HandlerInterface; use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; -use Tienvx\Bundle\MbtBundle\Service\StepsBuilderInterface; -use Tienvx\Bundle\MbtBundle\Service\StepsRunnerInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Builder\StepsBuilderInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; -class HandlerTestCase extends TestCase +abstract class HandlerTestCase extends TestCase { protected HandlerInterface $handler; protected BugRepositoryInterface $bugRepository; protected MessageBusInterface $messageBus; - protected StepsRunnerInterface $stepsRunner; + protected BugStepsRunner $stepsRunner; protected StepsBuilderInterface $stepsBuilder; protected array $newSteps; protected BugInterface $bug; @@ -31,7 +31,7 @@ protected function setUp(): void { $this->bugRepository = $this->createMock(BugRepositoryInterface::class); $this->messageBus = $this->createMock(MessageBusInterface::class); - $this->stepsRunner = $this->createMock(StepsRunnerInterface::class); + $this->stepsRunner = $this->createMock(BugStepsRunner::class); $this->stepsBuilder = $this->createMock(StepsBuilderInterface::class); $this->newSteps = [ $this->createMock(StepInterface::class), @@ -49,6 +49,7 @@ protected function setUp(): void $this->createMock(StepInterface::class), $this->createMock(StepInterface::class), ]); + $this->bug->setDebug(true); $this->stepsBuilder ->expects($this->once()) ->method('create') @@ -77,7 +78,6 @@ public function testHandle(?Throwable $exception, bool $updateSteps): void ->with( $this->newSteps, $this->bug, - false, $this->callback(function (callable $exceptionCallback) use ($exception) { if ($exception) { $exceptionCallback($exception); @@ -101,6 +101,7 @@ public function testHandle(?Throwable $exception, bool $updateSteps): void $this->messageBus->expects($this->never())->method('dispatch'); } $this->handler->handle($this->bug, 1, 2); + $this->assertFalse($this->bug->isDebug()); } public function exceptionProvider(): array diff --git a/tests/Reducer/Random/RandomHandlerTest.php b/tests/Reducer/Random/RandomHandlerTest.php index 4653018b..3a1b866a 100644 --- a/tests/Reducer/Random/RandomHandlerTest.php +++ b/tests/Reducer/Random/RandomHandlerTest.php @@ -16,6 +16,7 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Bug\Step * @uses \Tienvx\Bundle\MbtBundle\Message\ReduceBugMessage * @uses \Tienvx\Bundle\MbtBundle\Model\Progress + * @uses \Tienvx\Bundle\MbtBundle\Model\Debug */ class RandomHandlerTest extends HandlerTestCase { diff --git a/tests/Reducer/Split/SplitHandlerTest.php b/tests/Reducer/Split/SplitHandlerTest.php index f8f43d6d..63515131 100644 --- a/tests/Reducer/Split/SplitHandlerTest.php +++ b/tests/Reducer/Split/SplitHandlerTest.php @@ -16,6 +16,7 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Bug\Step * @uses \Tienvx\Bundle\MbtBundle\Message\ReduceBugMessage * @uses \Tienvx\Bundle\MbtBundle\Model\Progress + * @uses \Tienvx\Bundle\MbtBundle\Model\Debug */ class SplitHandlerTest extends HandlerTestCase { diff --git a/tests/Repository/BugRepositoryTest.php b/tests/Repository/BugRepositoryTest.php index f502ce96..32aa9766 100644 --- a/tests/Repository/BugRepositoryTest.php +++ b/tests/Repository/BugRepositoryTest.php @@ -171,12 +171,4 @@ public function testStopRecordingBug(): void $this->bugRepository->stopRecording($this->bug); $this->assertFalse($this->bug->getVideo()->isRecording()); } - - public function testUpdateVideoErrorMessage(): void - { - $this->assertNull($this->bug->getVideo()->getErrorMessage()); - $this->manager->expects($this->once())->method('flush'); - $this->bugRepository->updateVideoErrorMessage($this->bug, 'Something wrong'); - $this->assertSame('Something wrong', $this->bug->getVideo()->getErrorMessage()); - } } diff --git a/tests/Service/Bug/BugHelperTest.php b/tests/Service/Bug/BugHelperTest.php index ab6ee3b7..6f99cfbb 100644 --- a/tests/Service/Bug/BugHelperTest.php +++ b/tests/Service/Bug/BugHelperTest.php @@ -23,7 +23,7 @@ use Tienvx\Bundle\MbtBundle\Service\Bug\BugHelperInterface; use Tienvx\Bundle\MbtBundle\Service\Bug\BugNotifierInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; -use Tienvx\Bundle\MbtBundle\Service\StepsRunnerInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; /** * @covers \Tienvx\Bundle\MbtBundle\Service\Bug\BugHelper @@ -37,6 +37,7 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Bug\Video * @uses \Tienvx\Bundle\MbtBundle\Message\ReportBugMessage * @uses \Tienvx\Bundle\MbtBundle\Message\RecordVideoMessage + * @uses \Tienvx\Bundle\MbtBundle\Model\Debug */ class BugHelperTest extends TestCase { @@ -44,7 +45,7 @@ class BugHelperTest extends TestCase protected BugRepositoryInterface $bugRepository; protected MessageBusInterface $messageBus; protected BugNotifierInterface $bugNotifier; - protected StepsRunnerInterface $stepsRunner; + protected BugStepsRunner $stepsRunner; protected ConfigInterface $config; protected BugHelperInterface $helper; protected BugInterface $bug; @@ -56,7 +57,7 @@ protected function setUp(): void $this->bugRepository = $this->createMock(BugRepositoryInterface::class); $this->messageBus = $this->createMock(MessageBusInterface::class); $this->bugNotifier = $this->createMock(BugNotifierInterface::class); - $this->stepsRunner = $this->createMock(StepsRunnerInterface::class); + $this->stepsRunner = $this->createMock(BugStepsRunner::class); $this->config = $this->createMock(ConfigInterface::class); $this->helper = new BugHelper( $this->reducerManager, @@ -77,20 +78,7 @@ protected function setUp(): void $this->createMock(StepInterface::class), $this->createMock(StepInterface::class), ]); - } - - public function testCreateBug(): void - { - $steps = [ - $this->createMock(StepInterface::class), - $this->createMock(StepInterface::class), - $this->createMock(StepInterface::class), - ]; - $bug = $this->helper->createBug($steps, 'Something wrong'); - $this->assertInstanceOf(BugInterface::class, $bug); - $this->assertSame($steps, $bug->getSteps()); - $this->assertSame('', $bug->getTitle()); - $this->assertSame('Something wrong', $bug->getMessage()); + $this->bug->setDebug(false); } public function testReduceMissingBug(): void @@ -248,7 +236,7 @@ public function testRunTaskAlreadyRunning(): void /** * @dataProvider exceptionProvider */ - public function testRecordVideo(?Throwable $exception, bool $updateVideoErrorMessage): void + public function testRecordVideo(?Throwable $exception): void { $this->stepsRunner ->expects($this->once()) @@ -256,7 +244,6 @@ public function testRecordVideo(?Throwable $exception, bool $updateVideoErrorMes ->with( $this->bug->getSteps(), $this->bug, - true, $this->callback(function (callable $exceptionCallback) use ($exception) { if ($exception) { $exceptionCallback($exception); @@ -265,25 +252,23 @@ public function testRecordVideo(?Throwable $exception, bool $updateVideoErrorMes return true; }) ); - if ($exception && $updateVideoErrorMessage) { - $this->bugRepository - ->expects($this->once()) - ->method('updateVideoErrorMessage') - ->with($this->bug, $exception->getMessage()); - } else { - $this->bugRepository->expects($this->never())->method('updateVideoErrorMessage'); - } $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn($this->bug); $this->bugRepository->expects($this->once())->method('startRecording')->with($this->bug); $this->bugRepository->expects($this->once())->method('stopRecording')->with($this->bug); $this->helper->recordVideo(123); + $this->assertTrue($this->bug->isDebug()); + if ($exception) { + $this->assertSame($exception->getMessage(), $this->bug->getVideo()->getErrorMessage()); + } else { + $this->assertNull($this->bug->getVideo()->getErrorMessage()); + } } public function exceptionProvider(): array { return [ - [null, false], - [new Exception('Something wrong'), true], + [null], + [new Exception('Something wrong')], ]; } } diff --git a/tests/Service/SelenoidHelperTest.php b/tests/Service/SelenoidHelperTest.php index cae4d0c0..4b37f382 100644 --- a/tests/Service/SelenoidHelperTest.php +++ b/tests/Service/SelenoidHelperTest.php @@ -3,6 +3,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service; use Facebook\WebDriver\Remote\DesiredCapabilities; +use Facebook\WebDriver\Remote\RemoteWebDriver; use PHPUnit\Framework\TestCase; use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Entity\Task; @@ -20,6 +21,7 @@ * @uses \Tienvx\Bundle\MbtBundle\Entity\Bug * @uses \Tienvx\Bundle\MbtBundle\Model\Bug * @uses \Tienvx\Bundle\MbtBundle\Model\Task\Browser + * @uses \Tienvx\Bundle\MbtBundle\Model\Debug */ class SelenoidHelperTest extends TestCase { @@ -31,7 +33,10 @@ class SelenoidHelperTest extends TestCase protected function setUp(): void { - $this->selenoidHelper = new SelenoidHelper(); + $this->selenoidHelper = $this->createPartialMock( + SelenoidHelper::class, + ['createDriverInternal', 'waitForVideoContainer'] + ); $this->selenoidHelper->setWebdriverUri($this->webdriverUri); $browser = new Browser(); $browser->setName('firefox'); @@ -47,11 +52,11 @@ protected function setUp(): void public function testGetLogUrl(): void { $this->assertSame( - 'http://localhost:4444/logs/task-123.log', + $this->webdriverUri . '/logs/task-123.log', $this->selenoidHelper->getLogUrl($this->task) ); $this->assertSame( - 'http://localhost:4444/logs/bug-234.log', + $this->webdriverUri . '/logs/bug-234.log', $this->selenoidHelper->getLogUrl($this->bug) ); } @@ -59,75 +64,62 @@ public function testGetLogUrl(): void public function testGetVideoUrl(): void { $this->assertSame( - 'http://localhost:4444/video/task-123.mp4', + $this->webdriverUri . '/video/task-123.mp4', $this->selenoidHelper->getVideoUrl($this->task) ); $this->assertSame( - 'http://localhost:4444/video/bug-234.mp4', + $this->webdriverUri . '/video/bug-234.mp4', $this->selenoidHelper->getVideoUrl($this->bug) ); } /** - * @dataProvider capabilitiesParameterProvider + * @dataProvider entityProvider */ - public function testGetCapabilities(bool $debug, bool $hasBug): void + public function testCreateDriver(string $type, bool $debug): void { - $this->assertCapabilities( - $this->selenoidHelper->getCapabilities($hasBug ? $this->bug : $this->task, $debug), - $debug, - $hasBug - ); + $this->{$type}->setDebug($debug); + $driver = $this->createMock(RemoteWebDriver::class); + $this->selenoidHelper + ->expects($this->once()) + ->method('createDriverInternal') + ->with( + $this->webdriverUri . '/wd/hub', + $this->callback(function (DesiredCapabilities $capabilities) use ($type, $debug) { + $this->assertCapabilities($capabilities, $type, $debug); + + return true; + }) + ) + ->willReturn($driver); + $this->selenoidHelper->expects($this->exactly($debug))->method('waitForVideoContainer'); + $this->assertSame($driver, $this->selenoidHelper->createDriver($this->{$type})); } - private function assertCapabilities(DesiredCapabilities $capabilities, bool $debug, bool $hasBug): void + private function assertCapabilities(DesiredCapabilities $capabilities, string $type, bool $debug): void { $this->assertSame($debug, $capabilities->is('enableVideo')); $this->assertSame($debug, $capabilities->is('enableLog')); $this->assertSame($this->task->getBrowser()->getVersion(), $capabilities->getVersion()); $this->assertSame($this->task->getBrowser()->getName(), $capabilities->getBrowserName()); $this->assertSame( - $debug ? ($hasBug ? "bug-{$this->bug->getId()}.log" : "task-{$this->task->getId()}.log") : null, + $debug ? "$type-{$this->{$type}->getId()}.log" : null, $capabilities->getCapability('logName') ); $this->assertSame( - $debug ? ($hasBug ? "bug-{$this->bug->getId()}.mp4" : "task-{$this->task->getId()}.mp4") : null, + $debug ? "$type-{$this->{$type}->getId()}.mp4" : null, $capabilities->getCapability('videoName') ); $this->assertSame($debug ? 60 : null, $capabilities->getCapability('videoFrameRate')); } - public function capabilitiesParameterProvider(): array + public function entityProvider(): array { return [ - [true, false], - [true, true], - [false, false], - [false, true], + ['task', false], + ['task', true], + ['bug', false], + ['bug', true], ]; } - - public function testGetLogName(): void - { - $this->assertSame( - 'task-123.log', - $this->selenoidHelper->getLogName($this->task) - ); - $this->assertSame( - 'bug-234.log', - $this->selenoidHelper->getLogName($this->bug) - ); - } - - public function testGetVideoName(): void - { - $this->assertSame( - 'task-123.mp4', - $this->selenoidHelper->getVideoName($this->task) - ); - $this->assertSame( - 'bug-234.mp4', - $this->selenoidHelper->getVideoName($this->bug) - ); - } } diff --git a/tests/Service/ShortestPathStepsBuilderTest.php b/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php similarity index 98% rename from tests/Service/ShortestPathStepsBuilderTest.php rename to tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php index 7a5333eb..81af963f 100644 --- a/tests/Service/ShortestPathStepsBuilderTest.php +++ b/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php @@ -1,6 +1,6 @@ stepsRunner = new BugStepsRunner($this->selenoidHelper, $this->stepRunner); + } + + protected function assertHandlingException(Exception $exception, array $bugSteps = []): void + { + $this->handleException + ->expects($this->once()) + ->method('__invoke') + ->with($exception); + } +} diff --git a/tests/Service/StepRunnerTest.php b/tests/Service/Step/Runner/StepRunnerTest.php similarity index 94% rename from tests/Service/StepRunnerTest.php rename to tests/Service/Step/Runner/StepRunnerTest.php index 78f7935a..ef709f5e 100644 --- a/tests/Service/StepRunnerTest.php +++ b/tests/Service/Step/Runner/StepRunnerTest.php @@ -1,6 +1,6 @@ revision = new Revision(); + $this->task = new Task(); + $this->task->setModelRevision($this->revision); + $this->bug = new Bug(); + $this->bug->setTask($this->task); + } + + protected function setUp(): void + { + $this->steps = [ + new Step([], new Color(), 0), + new Step([], new Color(), 1), + new Step([], new Color(), 2), + new Step([], new Color(), 3), + ]; + $this->stepRunner = $this->createMock(StepRunnerInterface::class); + $this->selenoidHelper = $this->createMock(SelenoidHelperInterface::class); + $this->driver = $this->createMock(RemoteWebDriver::class); + $this->handleException = $this->getMockBuilder(\stdClass::class) + ->addMethods(['__invoke']) + ->getMock(); + } + + /** + * @dataProvider entityProvider + */ + public function testRunCanNotCreateDriver(DebugInterface $entity): void + { + $exception = new Exception('can not create driver'); + $this->selenoidHelper + ->expects($this->once()) + ->method('createDriver') + ->with($entity) + ->willThrowException($exception); + $this->driver->expects($this->never())->method('quit'); + $this->assertRunSteps(); + $this->assertHandlingException($exception); + $this->stepsRunner->run($this->steps, $entity, $this->handleException); + } + + /** + * @dataProvider entityProvider + */ + public function testRunInvalidSteps(DebugInterface $entity): void + { + $this->expectExceptionObject( + new UnexpectedValueException(sprintf('Step must be instance of "%s".', StepInterface::class)) + ); + $this->selenoidHelper + ->expects($this->once()) + ->method('createDriver') + ->with($entity) + ->willReturn($this->driver); + $this->driver->expects($this->once())->method('quit'); + $this->assertRunSteps(); + $this->handleException->expects($this->never())->method('__invoke'); + $this->stepsRunner->run([new \stdClass()], $entity, $this->handleException); + } + + /** + * @dataProvider entityProvider + */ + public function testRunNotFoundBug(DebugInterface $entity): void + { + $this->selenoidHelper + ->expects($this->once()) + ->method('createDriver') + ->with($entity) + ->willReturn($this->driver); + $this->driver->expects($this->once())->method('quit'); + $this->assertRunSteps($this->steps); + $this->handleException->expects($this->never())->method('__invoke'); + $this->stepsRunner->run($this->steps, $entity, $this->handleException); + } + + /** + * @dataProvider entityProvider + */ + public function testRunFoundBugAtThirdStep(DebugInterface $entity): void + { + $bugSteps = array_slice($this->steps, 0, 3); + $exception = new Exception('Can not run the third step'); + $this->selenoidHelper + ->expects($this->once()) + ->method('createDriver') + ->with($entity) + ->willReturn($this->driver); + $this->driver->expects($this->once())->method('quit'); + $this->assertRunSteps($bugSteps, $exception); + $this->assertHandlingException($exception, $bugSteps); + $this->stepsRunner->run($this->steps, $entity, $this->handleException); + } + + abstract protected function assertHandlingException(Exception $exception, array $bugSteps = []): void; + + protected function assertRunSteps(array $steps = [], ?Exception $exception = null): void + { + if ($steps) { + $mock = $this->stepRunner + ->expects($this->exactly(count($steps))) + ->method('run') + ->withConsecutive(...array_map( + fn (StepInterface $step) => [$step, $this->revision, $this->driver], + $steps + )); + if ($exception) { + $mock->will($this->onConsecutiveCalls( + ...[...array_fill(0, count($steps) - 1, null), $this->throwException($exception)], + )); + } + } else { + $this->stepRunner->expects($this->never())->method('run'); + } + } + + public function entityProvider(): array + { + return [ + [$this->task], + [$this->bug], + ]; + } +} diff --git a/tests/Service/Step/Runner/TaskStepsRunnerTest.php b/tests/Service/Step/Runner/TaskStepsRunnerTest.php new file mode 100644 index 00000000..eed44269 --- /dev/null +++ b/tests/Service/Step/Runner/TaskStepsRunnerTest.php @@ -0,0 +1,76 @@ +config = $this->createMock(ConfigInterface::class); + $this->stepsRunner = new TaskStepsRunner($this->selenoidHelper, $this->stepRunner, $this->config); + } + + /** + * @dataProvider entityProvider + */ + public function testRunReachMax2Steps(DebugInterface $entity): void + { + $this->selenoidHelper + ->expects($this->once()) + ->method('createDriver') + ->with($entity) + ->willReturn($this->driver); + $this->driver->expects($this->once())->method('quit'); + $this->assertRunSteps(array_slice($this->steps, 0, 2), null, 2); + $this->handleException->expects($this->never())->method('__invoke'); + $this->stepsRunner->run($this->steps, $entity, $this->handleException); + } + + protected function assertHandlingException(Exception $exception, array $bugSteps = []): void + { + $this->handleException + ->expects($this->once()) + ->method('__invoke') + ->with($this->callback(fn (object $bug) => $bug instanceof BugInterface && + '' === $bug->getTitle() && + $bug->getMessage() === $exception->getMessage() && + !array_udiff( + $bug->getSteps(), + $bugSteps, + fn (StepInterface $step1, StepInterface $step2) => $step1->getPlaces() === $step2->getPlaces() && + $step1->getTransition() === $step2->getTransition() && + $step1->getColor()->getValues() === $step2->getColor()->getValues() + ))); + } + + protected function assertRunSteps(array $steps = [], ?Exception $exception = null, int $maxSteps = 99): void + { + parent::assertRunSteps($steps, $exception); + $this->config + ->expects($this->exactly($exception ? count($steps) - 1 : count($steps))) + ->method('getMaxSteps') + ->willReturn($maxSteps); + } +} diff --git a/tests/Service/StepsRunnerTest.php b/tests/Service/StepsRunnerTest.php deleted file mode 100644 index 8732dd33..00000000 --- a/tests/Service/StepsRunnerTest.php +++ /dev/null @@ -1,326 +0,0 @@ -revision = new Revision(); - $this->task = new Task(); - $this->task->setModelRevision($this->revision); - $this->bug = new Bug(); - $this->bug->setTask($this->task); - } - - protected function setUp(): void - { - $this->steps = [ - new Step([], new Color(), 0), - new Step([], new Color(), 1), - new Step([], new Color(), 2), - new Step([], new Color(), 3), - ]; - $this->stepRunner = $this->createMock(StepRunnerInterface::class); - $this->selenoidHelper = $this->createMock(SelenoidHelperInterface::class); - $this->stepsRunner = $this->getMockBuilder(StepsRunner::class) - ->onlyMethods(['waitForVideoContainer']) - ->setConstructorArgs([ - $this->selenoidHelper, - $this->stepRunner, - ]) - ->getMock(); - $this->driver = $this->createMock(RemoteWebDriver::class); - $this->capabilities = new DesiredCapabilities(); - $this->exceptionCallback = $this->getMockBuilder(\stdClass::class) - ->addMethods(['__invoke']) - ->getMock(); - $this->runCallback = $this->getMockBuilder(\stdClass::class) - ->addMethods(['__invoke']) - ->getMock(); - } - - /** - * @dataProvider entityProvider - */ - public function testRunCanNotCreateDriver( - TaskInterface|BugInterface $entity, - bool $debug, - bool $hasExceptionCallback, - bool $hasRunCallback - ): void { - $exception = new Exception('can not create driver'); - $this->selenoidHelper - ->expects($this->once()) - ->method('getCapabilities') - ->with($entity, $debug) - ->willReturn($this->capabilities); - $this->selenoidHelper - ->expects($this->once()) - ->method('createDriver') - ->with($this->capabilities) - ->willThrowException($exception); - $this->driver->expects($this->never())->method('quit'); - $this->stepRunner->expects($this->never())->method('run'); - if ($hasExceptionCallback) { - $this->exceptionCallback->expects($this->once())->method('__invoke')->with($exception, null); - } else { - $this->exceptionCallback->expects($this->never())->method('__invoke'); - } - $this->runCallback->expects($this->never())->method('__invoke'); - $this->stepsRunner->run( - $this->steps, - $entity, - $debug, - $hasExceptionCallback ? $this->exceptionCallback : null, - $hasRunCallback ? $this->runCallback : null - ); - } - - /** - * @dataProvider entityProvider - */ - public function testRunInvalidSteps( - TaskInterface|BugInterface $entity, - bool $debug, - bool $hasExceptionCallback, - bool $hasRunCallback - ): void { - $this->expectExceptionObject( - new UnexpectedValueException(sprintf('Step must be instance of "%s".', StepInterface::class)) - ); - $this->selenoidHelper - ->expects($this->once()) - ->method('getCapabilities') - ->with($entity, $debug) - ->willReturn($this->capabilities); - $this->selenoidHelper - ->expects($this->once()) - ->method('createDriver') - ->with($this->capabilities) - ->willReturn($this->driver); - $this->driver->expects($this->once())->method('quit'); - $this->stepRunner->expects($this->never())->method('run'); - $this->exceptionCallback->expects($this->never())->method('__invoke'); - $this->runCallback->expects($this->never())->method('__invoke'); - $this->stepsRunner->expects($this->exactly($debug))->method('waitForVideoContainer'); - $this->stepsRunner->run( - [new \stdClass()], - $entity, - $debug, - $hasExceptionCallback ? $this->exceptionCallback : null, - $hasRunCallback ? $this->runCallback : null - ); - } - - /** - * @dataProvider entityProvider - */ - public function testRunNotFoundBugAndNotReachMaxSteps( - TaskInterface|BugInterface $entity, - bool $debug, - bool $hasExceptionCallback, - bool $hasRunCallback - ): void { - $this->selenoidHelper - ->expects($this->once()) - ->method('getCapabilities') - ->with($entity, $debug) - ->willReturn($this->capabilities); - $this->selenoidHelper - ->expects($this->once()) - ->method('createDriver') - ->with($this->capabilities) - ->willReturn($this->driver); - $this->driver->expects($this->once())->method('quit'); - $this->stepRunner - ->expects($this->exactly(count($this->steps))) - ->method('run') - ->withConsecutive(...array_map( - fn (StepInterface $step) => [$step, $this->revision, $this->driver], - $this->steps - )); - $this->exceptionCallback->expects($this->never())->method('__invoke'); - $this->runCallback - ->expects($this->exactly($hasRunCallback ? count($this->steps) : 0)) - ->method('__invoke') - ->withConsecutive(...array_map( - fn (StepInterface $step) => [$step], - $this->steps - )) - ->willReturn(false); - $this->stepsRunner->expects($this->exactly($debug))->method('waitForVideoContainer'); - $this->stepsRunner->run( - $this->steps, - $entity, - $debug, - $hasExceptionCallback ? $this->exceptionCallback : null, - $hasRunCallback ? $this->runCallback : null - ); - } - - /** - * @dataProvider entityProvider - */ - public function testRunFoundBugAtThirdStep( - TaskInterface|BugInterface $entity, - bool $debug, - bool $hasExceptionCallback, - bool $hasRunCallback - ): void { - $exception = new Exception('Can not run the third step'); - $this->selenoidHelper - ->expects($this->once()) - ->method('getCapabilities') - ->with($entity, $debug) - ->willReturn($this->capabilities); - $this->selenoidHelper - ->expects($this->once()) - ->method('createDriver') - ->with($this->capabilities) - ->willReturn($this->driver); - $this->driver->expects($this->once())->method('quit'); - $this->stepRunner - ->expects($this->exactly(3)) - ->method('run') - ->withConsecutive(...array_map( - fn (StepInterface $step) => [$step, $this->revision, $this->driver], - array_slice($this->steps, 0, 3) - )) - ->will($this->onConsecutiveCalls( - null, - null, - $this->throwException($exception), - )); - $this->exceptionCallback - ->expects($this->exactly($hasExceptionCallback)) - ->method('__invoke') - ->with($exception, $this->steps[2]); - $this->runCallback - ->expects($this->exactly($hasRunCallback ? 2 : 0)) - ->method('__invoke') - ->withConsecutive(...array_map( - fn (StepInterface $step) => [$step], - array_slice($this->steps, 0, 2) - )) - ->willReturn(false); - $this->stepsRunner->expects($this->exactly($debug))->method('waitForVideoContainer'); - $this->stepsRunner->run( - $this->steps, - $entity, - $debug, - $hasExceptionCallback ? $this->exceptionCallback : null, - $hasRunCallback ? $this->runCallback : null - ); - } - - /** - * @dataProvider entityProvider - */ - public function testRunReachMaxSteps2( - TaskInterface|BugInterface $entity, - bool $debug, - bool $hasExceptionCallback, - bool $hasRunCallback - ): void { - $this->selenoidHelper - ->expects($this->once()) - ->method('getCapabilities') - ->with($entity, $debug) - ->willReturn($this->capabilities); - $this->selenoidHelper - ->expects($this->once()) - ->method('createDriver') - ->with($this->capabilities) - ->willReturn($this->driver); - $this->driver->expects($this->once())->method('quit'); - $this->stepRunner - ->expects($this->exactly($hasRunCallback ? 2 : count($this->steps))) - ->method('run') - ->withConsecutive(...array_map( - fn (StepInterface $step) => [$step, $this->revision, $this->driver], - $hasRunCallback ? array_slice($this->steps, 0, 2) : $this->steps - )); - $this->exceptionCallback->expects($this->never())->method('__invoke'); - $this->runCallback - ->expects($this->exactly($hasRunCallback ? 2 : 0)) - ->method('__invoke') - ->withConsecutive(...array_map( - fn (StepInterface $step) => [$step], - array_slice($this->steps, 0, 2) - )) - ->willReturnOnConsecutiveCalls(false, true); - $this->stepsRunner->expects($this->exactly($debug))->method('waitForVideoContainer'); - $this->stepsRunner->run( - $this->steps, - $entity, - $debug, - $hasExceptionCallback ? $this->exceptionCallback : null, - $hasRunCallback ? $this->runCallback : null - ); - } - - public function entityProvider(): array - { - return [ - [$this->task, true, false, false], - [$this->task, true, true, false], - [$this->task, true, false, true], - [$this->task, true, true, true], - [$this->task, false, false, false], - [$this->task, false, true, false], - [$this->task, false, false, true], - [$this->task, false, true, true], - [$this->bug, true, false, false], - [$this->bug, true, true, false], - [$this->bug, true, false, true], - [$this->bug, true, true, true], - [$this->bug, false, false, false], - [$this->bug, false, true, false], - [$this->bug, false, false, true], - [$this->bug, false, true, true], - ]; - } -} diff --git a/tests/Service/Task/TaskHelperTest.php b/tests/Service/Task/TaskHelperTest.php index d68de505..c0527f56 100644 --- a/tests/Service/Task/TaskHelperTest.php +++ b/tests/Service/Task/TaskHelperTest.php @@ -2,23 +2,19 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service\Task; -use Exception; use PHPUnit\Framework\TestCase; use SingleColorPetrinet\Model\Color; use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException; -use Throwable; use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Entity\Task; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; use Tienvx\Bundle\MbtBundle\Generator\GeneratorInterface; use Tienvx\Bundle\MbtBundle\Generator\GeneratorManagerInterface; -use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; use Tienvx\Bundle\MbtBundle\Model\BugInterface; use Tienvx\Bundle\MbtBundle\Model\TaskInterface; use Tienvx\Bundle\MbtBundle\Repository\TaskRepositoryInterface; -use Tienvx\Bundle\MbtBundle\Service\Bug\BugHelperInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; -use Tienvx\Bundle\MbtBundle\Service\StepsRunnerInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\TaskStepsRunner; use Tienvx\Bundle\MbtBundle\Service\Task\TaskHelper; use Tienvx\Bundle\MbtBundle\Service\Task\TaskHelperInterface; use Tienvx\Bundle\MbtBundle\ValueObject\Bug\Step; @@ -32,18 +28,17 @@ * @uses \Tienvx\Bundle\MbtBundle\Entity\Bug * @uses \Tienvx\Bundle\MbtBundle\Model\Bug * @uses \Tienvx\Bundle\MbtBundle\Model\Bug\Step + * @uses \Tienvx\Bundle\MbtBundle\Model\Debug */ class TaskHelperTest extends TestCase { protected array $steps; protected GeneratorManagerInterface $generatorManager; protected TaskRepositoryInterface $taskRepository; - protected StepsRunnerInterface $stepsRunner; - protected BugHelperInterface $bugHelper; + protected TaskStepsRunner $stepsRunner; protected TaskHelperInterface $taskHelper; protected ConfigInterface $config; protected TaskInterface $task; - protected BugInterface $bug; protected function setUp(): void { @@ -55,21 +50,18 @@ protected function setUp(): void ]; $this->generatorManager = $this->createMock(GeneratorManagerInterface::class); $this->taskRepository = $this->createMock(TaskRepositoryInterface::class); - $this->stepsRunner = $this->createMock(StepsRunnerInterface::class); - $this->bugHelper = $this->createMock(BugHelperInterface::class); + $this->stepsRunner = $this->createMock(TaskStepsRunner::class); $this->config = $this->createMock(ConfigInterface::class); $this->taskHelper = new TaskHelper( $this->generatorManager, $this->taskRepository, $this->stepsRunner, - $this->bugHelper, $this->config ); $this->task = new Task(); $this->task->setId(123); $this->task->setRunning(false); $this->task->setDebug(true); - $this->bug = new Bug(); } public function testRunNoTask(): void @@ -90,9 +82,9 @@ public function testRunTaskAlreadyRunning(): void } /** - * @dataProvider stepProvider + * @dataProvider bugProvider */ - public function testRun(?Throwable $exception, ?StepInterface $step): void + public function testRun(?BugInterface $bug): void { $this->taskRepository->expects($this->once())->method('find')->with(123)->willReturn($this->task); $this->taskRepository->expects($this->once())->method('startRunning')->with($this->task); @@ -107,56 +99,27 @@ public function testRun(?Throwable $exception, ?StepInterface $step): void ->with( $this->steps, $this->task, - $this->task->isDebug(), - $this->callback(function (callable $exceptionCallback) use ($exception, $step) { - if ($exception) { - $exceptionCallback($exception, $step); - } - - return true; - }), - $this->callback(function (callable $runCallback) use ($exception, $step) { - if (!$exception && $step) { - $this->assertFalse($runCallback($step)); + $this->callback(function (callable $exceptionCallback) use ($bug) { + if ($bug) { + $exceptionCallback($bug); } return true; }) ); - if ($exception) { - $this->bugHelper - ->expects($this->once()) - ->method('createBug') - ->with($step ? [$step] : [], $exception->getMessage()) - ->willReturn($this->bug); - } else { - $this->bugHelper->expects($this->never())->method('createBug'); - } - if (!$exception && $step) { - $this->config - ->expects($this->once()) - ->method('getMaxSteps') - ->willReturn(150); - } else { - $this->config->expects($this->never())->method('getMaxSteps'); - } $this->taskHelper->run(123); - if ($exception) { - $this->assertSame([$this->bug], $this->task->getBugs()->toArray()); + if ($bug) { + $this->assertSame([$bug], $this->task->getBugs()->toArray()); } else { $this->assertEmpty($this->task->getBugs()); } } - public function stepProvider(): array + public function bugProvider(): array { - $step = new Step([], new Color(), 0); - return [ - [null, null], - [null, $step], - [new Exception('Something wrong'), null], - [new Exception('Caught a bug'), $step], + [null], + [new Bug()], ]; } } From f028e90abc6762a8b88da6b9bc40b6a07709f47b Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 16 Apr 2022 19:36:05 +0700 Subject: [PATCH 33/85] Test debug model --- tests/Entity/BugTest.php | 1 + tests/Entity/TaskTest.php | 2 +- tests/Model/BugTest.php | 3 +++ tests/Model/TaskTest.php | 3 ++- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/Entity/BugTest.php b/tests/Entity/BugTest.php index 73a15d6f..3d3e271f 100644 --- a/tests/Entity/BugTest.php +++ b/tests/Entity/BugTest.php @@ -10,6 +10,7 @@ /** * @covers \Tienvx\Bundle\MbtBundle\Entity\Bug * @covers \Tienvx\Bundle\MbtBundle\Model\Bug + * @covers \Tienvx\Bundle\MbtBundle\Model\Debug * * @uses \Tienvx\Bundle\MbtBundle\Entity\Task * @uses \Tienvx\Bundle\MbtBundle\Model\Task diff --git a/tests/Entity/TaskTest.php b/tests/Entity/TaskTest.php index 99d68246..be0c5853 100644 --- a/tests/Entity/TaskTest.php +++ b/tests/Entity/TaskTest.php @@ -10,6 +10,7 @@ /** * @covers \Tienvx\Bundle\MbtBundle\Entity\Task * @covers \Tienvx\Bundle\MbtBundle\Model\Task + * @covers \Tienvx\Bundle\MbtBundle\Model\Debug * * @uses \Tienvx\Bundle\MbtBundle\Model\Bug * @uses \Tienvx\Bundle\MbtBundle\Entity\Bug @@ -17,7 +18,6 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision * @uses \Tienvx\Bundle\MbtBundle\Entity\Task\Browser * @uses \Tienvx\Bundle\MbtBundle\Model\Task\Browser - * @uses \Tienvx\Bundle\MbtBundle\Model\Debug */ class TaskTest extends TaskModelTest { diff --git a/tests/Model/BugTest.php b/tests/Model/BugTest.php index 70a472cf..02cc2403 100644 --- a/tests/Model/BugTest.php +++ b/tests/Model/BugTest.php @@ -15,6 +15,7 @@ /** * @covers \Tienvx\Bundle\MbtBundle\Model\Bug + * @covers \Tienvx\Bundle\MbtBundle\Model\Debug * * @uses \Tienvx\Bundle\MbtBundle\Entity\Task * @uses \Tienvx\Bundle\MbtBundle\Model\Task @@ -49,6 +50,7 @@ protected function setUp(): void $this->bug->setProgress($this->progress); $this->bug->setVideo($this->video); $this->bug->setClosed(true); + $this->bug->setDebug(true); } public function testProperties(): void @@ -61,6 +63,7 @@ public function testProperties(): void $this->assertSame($this->progress, $this->bug->getProgress()); $this->assertSame(true, $this->bug->isClosed()); $this->assertSame($this->video, $this->bug->getVideo()); + $this->assertSame(true, $this->bug->isDebug()); } public function testGetLogName(): void diff --git a/tests/Model/TaskTest.php b/tests/Model/TaskTest.php index 02c4a34b..6bb2b770 100644 --- a/tests/Model/TaskTest.php +++ b/tests/Model/TaskTest.php @@ -12,6 +12,7 @@ /** * @covers \Tienvx\Bundle\MbtBundle\Model\Task + * @covers \Tienvx\Bundle\MbtBundle\Model\Debug * * @uses \Tienvx\Bundle\MbtBundle\Model\Bug * @uses \Tienvx\Bundle\MbtBundle\Entity\Bug @@ -19,7 +20,6 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision * @uses \Tienvx\Bundle\MbtBundle\Entity\Task\Browser * @uses \Tienvx\Bundle\MbtBundle\Model\Task\Browser - * @uses \Tienvx\Bundle\MbtBundle\Model\Debug */ class TaskTest extends TestCase { @@ -56,6 +56,7 @@ public function testProperties(): void $this->assertSame($this->browser, $this->task->getBrowser()); $this->assertSame($this->bugs, $this->task->getBugs()->toArray()); $this->assertSame(true, $this->task->isDebug()); + $this->assertSame($this->task, $this->task->getTask()); } public function testGetLogName(): void From 24619fd6dafb5c4170d048f25c37ef7ac33d035f Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 16 Apr 2022 19:48:07 +0700 Subject: [PATCH 34/85] Fix service definitions --- src/Resources/config/services.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index e6d0ca1f..166b9f05 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -64,10 +64,9 @@ use Tienvx\Bundle\MbtBundle\Service\SelenoidHelperInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Builder\ShortestPathStepsBuilder; use Tienvx\Bundle\MbtBundle\Service\Step\Builder\StepsBuilderInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepRunner; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepRunnerInterface; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepsRunner; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepsRunnerInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\TaskStepsRunner; use Tienvx\Bundle\MbtBundle\Service\Task\TaskHelper; use Tienvx\Bundle\MbtBundle\Service\Task\TaskHelperInterface; @@ -135,7 +134,7 @@ ->args([ service(BugRepositoryInterface::class), service(MessageBusInterface::class), - service(StepsRunnerInterface::class), + service(BugStepsRunner::class), service(StepsBuilderInterface::class), ]) ->set(RandomReducer::class) @@ -152,7 +151,7 @@ ->args([ service(BugRepositoryInterface::class), service(MessageBusInterface::class), - service(StepsRunnerInterface::class), + service(BugStepsRunner::class), service(StepsBuilderInterface::class), ]) ->set(SplitReducer::class) @@ -231,7 +230,7 @@ service(BugRepositoryInterface::class), service(MessageBusInterface::class), service(BugNotifierInterface::class), - service(StepsRunnerInterface::class), + service(BugStepsRunner::class), service(ConfigInterface::class), ]) ->alias(BugHelperInterface::class, BugHelper::class) @@ -265,12 +264,16 @@ ]) ->alias(StepRunnerInterface::class, StepRunner::class) - ->set(StepsRunner::class) + ->set(TaskStepsRunner::class) + ->args([ + service(SelenoidHelperInterface::class), + service(StepRunnerInterface::class), + ]) + ->set(BugStepsRunner::class) ->args([ service(SelenoidHelperInterface::class), service(StepRunnerInterface::class), ]) - ->alias(StepsRunnerInterface::class, StepsRunner::class) ->set(MarkingHelper::class) ->args([ From 2b7e054a3859d2a100de6588ee8290afea8efbc6 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 16 Apr 2022 23:50:30 +0700 Subject: [PATCH 35/85] Only store different error message on recording bug --- src/Service/Bug/BugHelper.php | 4 +++- tests/Service/Bug/BugHelperTest.php | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Service/Bug/BugHelper.php b/src/Service/Bug/BugHelper.php index 42f60a00..9dd6ed10 100644 --- a/src/Service/Bug/BugHelper.php +++ b/src/Service/Bug/BugHelper.php @@ -96,7 +96,9 @@ public function recordVideo(int $bugId): void $this->bugRepository->startRecording($bug); $bug->setDebug(true); $this->stepsRunner->run($bug->getSteps(), $bug, function (Throwable $throwable) use ($bug) { - $bug->getVideo()->setErrorMessage($throwable->getMessage()); + $bug->getVideo()->setErrorMessage( + $throwable->getMessage() !== $bug->getMessage() ? $throwable->getMessage() : null + ); }); $this->bugRepository->stopRecording($bug); } diff --git a/tests/Service/Bug/BugHelperTest.php b/tests/Service/Bug/BugHelperTest.php index 6f99cfbb..ffff182d 100644 --- a/tests/Service/Bug/BugHelperTest.php +++ b/tests/Service/Bug/BugHelperTest.php @@ -72,6 +72,7 @@ protected function setUp(): void $this->progress->setProcessed(9); $this->bug = new Bug(); $this->bug->setProgress($this->progress); + $this->bug->setMessage('Something wrong'); $this->bug->setId(123); $this->bug->setSteps([ $this->createMock(StepInterface::class), @@ -257,7 +258,7 @@ public function testRecordVideo(?Throwable $exception): void $this->bugRepository->expects($this->once())->method('stopRecording')->with($this->bug); $this->helper->recordVideo(123); $this->assertTrue($this->bug->isDebug()); - if ($exception) { + if ($exception && $exception->getMessage() !== $this->bug->getMessage()) { $this->assertSame($exception->getMessage(), $this->bug->getVideo()->getErrorMessage()); } else { $this->assertNull($this->bug->getVideo()->getErrorMessage()); @@ -269,6 +270,7 @@ public function exceptionProvider(): array return [ [null], [new Exception('Something wrong')], + [new Exception('Something else wrong')], ]; } } From 6cd46027f894ce6de4d4f2f03d010f8cc572580c Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 17 Apr 2022 11:30:25 +0700 Subject: [PATCH 36/85] Fix missing constructor parameter for TaskStepsRunner --- src/Resources/config/services.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index 166b9f05..0ff91385 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -268,6 +268,7 @@ ->args([ service(SelenoidHelperInterface::class), service(StepRunnerInterface::class), + service(ConfigInterface::class), ]) ->set(BugStepsRunner::class) ->args([ From 99c2167dd1fd890336b0d250324d353078ccf670 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 17 Apr 2022 22:54:48 +0700 Subject: [PATCH 37/85] Reuse start transition checking code --- src/Model/Model/Revision/Transition.php | 5 + .../Model/Revision/TransitionInterface.php | 2 + src/Service/Model/ModelHelper.php | 4 +- src/Service/Petrinet/PetrinetHelper.php | 9 +- tests/Model/Model/Revision/TransitionTest.php | 8 ++ tests/Service/Petrinet/PetrinetHelperTest.php | 108 +++++++++++++----- 6 files changed, 96 insertions(+), 40 deletions(-) diff --git a/src/Model/Model/Revision/Transition.php b/src/Model/Model/Revision/Transition.php index 4f94f15c..a0afe081 100644 --- a/src/Model/Model/Revision/Transition.php +++ b/src/Model/Model/Revision/Transition.php @@ -96,4 +96,9 @@ public function toArray(): array 'commands' => array_map(fn (CommandInterface $command) => $command->toArray(), $this->commands), ]; } + + public function isStart(): bool + { + return empty($this->fromPlaces); + } } diff --git a/src/Model/Model/Revision/TransitionInterface.php b/src/Model/Model/Revision/TransitionInterface.php index 0191cf25..6e1938e4 100644 --- a/src/Model/Model/Revision/TransitionInterface.php +++ b/src/Model/Model/Revision/TransitionInterface.php @@ -31,4 +31,6 @@ public function setToPlaces(array $toPlaces): void; public function addToPlace(int $toPlace): void; public function toArray(): array; + + public function isStart(): bool; } diff --git a/src/Service/Model/ModelHelper.php b/src/Service/Model/ModelHelper.php index 5328d6aa..7c3ca949 100644 --- a/src/Service/Model/ModelHelper.php +++ b/src/Service/Model/ModelHelper.php @@ -14,7 +14,7 @@ class ModelHelper implements ModelHelperInterface public function getStartTransitionId(RevisionInterface $revision): int { foreach ($revision->getTransitions() as $index => $transition) { - if ($transition instanceof TransitionInterface && 0 === count($transition->getFromPlaces())) { + if ($transition instanceof TransitionInterface && $transition->isStart()) { return $index; } } @@ -28,7 +28,7 @@ public function getStartTransitionId(RevisionInterface $revision): int public function getStartPlaceIds(RevisionInterface $revision): array { foreach ($revision->getTransitions() as $transition) { - if ($transition instanceof TransitionInterface && 0 === count($transition->getFromPlaces())) { + if ($transition instanceof TransitionInterface && $transition->isStart()) { return array_fill_keys($transition->getToPlaces(), 1); } } diff --git a/src/Service/Petrinet/PetrinetHelper.php b/src/Service/Petrinet/PetrinetHelper.php index a53ffe62..d84e8bd1 100644 --- a/src/Service/Petrinet/PetrinetHelper.php +++ b/src/Service/Petrinet/PetrinetHelper.php @@ -30,7 +30,7 @@ public function build(RevisionInterface $revision): PetrinetInterface $places = $this->getPlaces($revision, $builder); $transitions = $this->getTransitions($revision, $builder); foreach ($revision->getTransitions() as $index => $transition) { - if ($transition instanceof TransitionInterface && $this->isValidTransition($transition)) { + if ($transition instanceof TransitionInterface && !$transition->isStart()) { $this->connectPlacesToTransition( array_intersect_key($places, array_flip($transition->getFromPlaces())), $transitions[$index], @@ -60,7 +60,7 @@ protected function getTransitions(RevisionInterface $revision, SingleColorPetrin { $transitions = []; foreach ($revision->getTransitions() as $index => $transition) { - if ($transition instanceof TransitionInterface && $this->isValidTransition($transition)) { + if ($transition instanceof TransitionInterface && !$transition->isStart()) { $guardCallback = $transition->getGuard() ? fn (ColorInterface $color): bool => (bool) $this->expressionLanguage->evaluate( $transition->getGuard(), @@ -97,9 +97,4 @@ protected function connectTransitionToPlaces( $builder->connect($transition, $places[$toPlace], 1); } } - - protected function isValidTransition(TransitionInterface $transition): bool - { - return !empty($transition->getFromPlaces()); - } } diff --git a/tests/Model/Model/Revision/TransitionTest.php b/tests/Model/Model/Revision/TransitionTest.php index 3c6f4676..4c728355 100644 --- a/tests/Model/Model/Revision/TransitionTest.php +++ b/tests/Model/Model/Revision/TransitionTest.php @@ -49,6 +49,13 @@ protected function setUpCommands(): void $this->command2->setValue(null); } + public function testStartTransition(): void + { + $this->assertFalse($this->transition->isStart()); + $this->transition->setFromPlaces([]); + $this->assertTrue($this->transition->isStart()); + } + public function testSerialize(): void { $className = get_class($this->transition); @@ -70,6 +77,7 @@ public function testUnerialize(): void $this->assertSame(StoreCommandRunner::STORE, $transition->getCommands()[0]->getCommand()); $this->assertSame('55', $transition->getCommands()[0]->getTarget()); $this->assertSame('number', $transition->getCommands()[0]->getValue()); + $this->assertFalse($transition->isStart()); } protected function createTransition(): TransitionInterface diff --git a/tests/Service/Petrinet/PetrinetHelperTest.php b/tests/Service/Petrinet/PetrinetHelperTest.php index dcd5a934..3676c23a 100644 --- a/tests/Service/Petrinet/PetrinetHelperTest.php +++ b/tests/Service/Petrinet/PetrinetHelperTest.php @@ -2,11 +2,12 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service\Petrinet; -use Petrinet\Model\PlaceInterface; +use Petrinet\Model\ArcInterface; use PHPUnit\Framework\TestCase; use SingleColorPetrinet\Model\Color; use SingleColorPetrinet\Model\ColorfulFactory; -use SingleColorPetrinet\Model\GuardedTransitionInterface; +use SingleColorPetrinet\Model\GuardedTransition as PetrinetTransition; +use SingleColorPetrinet\Model\Place as PetrinetPlace; use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; use Tienvx\Bundle\MbtBundle\Service\ExpressionLanguage; use Tienvx\Bundle\MbtBundle\Service\Petrinet\PetrinetHelper; @@ -28,45 +29,90 @@ public function testBuild(): void $factory = new ColorfulFactory(); $expressionLanguage = new ExpressionLanguage(); $helper = new PetrinetHelper($factory, $expressionLanguage); - $places = [ + + // Model revision + $revision = new Revision(); + $revision->setPlaces([ + $place0 = new Place(), $place1 = new Place(), $place2 = new Place(), - $place3 = new Place(), - ]; - $revision = new Revision(); - $revision->setPlaces($places); - $transitions = [ + ]); + $revision->setTransitions([ + $transition0 = new Transition(), $transition1 = new Transition(), $transition2 = new Transition(), - ]; + $transition3 = new Transition(), + ]); + $transition0->setFromPlaces([]); + $transition0->setToPlaces([0]); $transition1->setGuard('count > 0'); - $transition1->setFromPlaces([0, 1]); - $transition1->setToPlaces([2]); + $transition1->setFromPlaces([0]); + $transition1->setToPlaces([1, 2]); $transition2->setFromPlaces([2]); $transition2->setToPlaces([1]); - $revision->setTransitions($transitions); + $transition3->setFromPlaces([1]); + $transition3->setToPlaces([0, 2]); + + // Petrinet $petrinet = $helper->build($revision); $this->assertCount(3, $petrinet->getPlaces()); - foreach ($petrinet->getPlaces() as $place) { - $this->assertInstanceOf(PlaceInterface::class, $place); + $places = [ + 0 => [ + 'input' => [3], + 'output' => [1], + ], + 1 => [ + 'input' => [1, 2], + 'output' => [3], + ], + 2 => [ + 'input' => [1, 3], + 'output' => [2], + ], + ]; + foreach ($petrinet->getPlaces() as $index => $place) { + $this->assertInstanceOf(PetrinetPlace::class, $place); + $this->assertSame( + $places[$index]['input'], + array_map(fn (ArcInterface $arc) => $arc->getTransition()->getId(), $place->getInputArcs()->toArray()), + ); + $this->assertSame( + $places[$index]['output'], + array_map(fn (ArcInterface $arc) => $arc->getTransition()->getId(), $place->getOutputArcs()->toArray()), + ); } - $this->assertCount(0, $petrinet->getPlaces()[0]->getInputArcs()); - $this->assertCount(1, $petrinet->getPlaces()[0]->getOutputArcs()); - $this->assertCount(1, $petrinet->getPlaces()[1]->getInputArcs()); - $this->assertCount(1, $petrinet->getPlaces()[1]->getOutputArcs()); - $this->assertCount(1, $petrinet->getPlaces()[2]->getInputArcs()); - $this->assertCount(1, $petrinet->getPlaces()[2]->getOutputArcs()); - $this->assertCount(2, $petrinet->getTransitions()); - foreach ($petrinet->getTransitions() as $place) { - $this->assertInstanceOf(GuardedTransitionInterface::class, $place); + $this->assertCount(3, $petrinet->getTransitions()); + $transitions = [ + 0 => [ + 'input' => [0], + 'output' => [1, 2], + ], + 1 => [ + 'input' => [2], + 'output' => [1], + ], + 2 => [ + 'input' => [1], + 'output' => [0, 2], + ], + ]; + foreach ($petrinet->getTransitions() as $index => $transition) { + $this->assertInstanceOf(PetrinetTransition::class, $transition); + $this->assertSame( + $transitions[$index]['input'], + array_map(fn (ArcInterface $arc) => $arc->getPlace()->getId(), $transition->getInputArcs()->toArray()), + ); + $this->assertSame( + $transitions[$index]['output'], + array_map(fn (ArcInterface $arc) => $arc->getPlace()->getId(), $transition->getOutputArcs()->toArray()), + ); + if (0 === $index) { + $this->assertIsCallable($guardCallback = $transition->getGuard()); + $this->assertTrue($guardCallback(new Color(['count' => 1]))); + $this->assertFalse($guardCallback(new Color(['count' => 0]))); + } else { + $this->assertNull($transition->getGuard()); + } } - $this->assertIsCallable($guardCallback = $petrinet->getTransitions()[0]->getGuard()); - $this->assertTrue($guardCallback(new Color(['count' => 1]))); - $this->assertFalse($guardCallback(new Color(['count' => 0]))); - $this->assertNull($petrinet->getTransitions()[1]->getGuard()); - $this->assertCount(2, $petrinet->getTransitions()[0]->getInputArcs()); - $this->assertCount(1, $petrinet->getTransitions()[0]->getOutputArcs()); - $this->assertCount(1, $petrinet->getTransitions()[1]->getInputArcs()); - $this->assertCount(1, $petrinet->getTransitions()[1]->getOutputArcs()); } } From 0027d46300c1ad8ed27b7d7a71e1729268691c54 Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 18 Apr 2022 00:43:01 +0700 Subject: [PATCH 38/85] Set min pair length (for dispatchers) --- src/Reducer/DispatcherTemplate.php | 6 ++- src/Reducer/Random/RandomDispatcher.php | 12 ++++- src/Reducer/Split/SplitDispatcher.php | 11 +++- tests/Reducer/DispatcherTestCase.php | 49 +++++++---------- tests/Reducer/Random/RandomDispatcherTest.php | 26 +++++++++ tests/Reducer/Split/SplitDispatcherTest.php | 54 ++++++++++++++++--- 6 files changed, 117 insertions(+), 41 deletions(-) diff --git a/src/Reducer/DispatcherTemplate.php b/src/Reducer/DispatcherTemplate.php index fd083d55..7c5c7459 100644 --- a/src/Reducer/DispatcherTemplate.php +++ b/src/Reducer/DispatcherTemplate.php @@ -8,6 +8,8 @@ abstract class DispatcherTemplate implements DispatcherInterface { + protected const MIN_PAIR_LENGTH = 2; // 3 steps + protected MessageBusInterface $messageBus; public function __construct(MessageBusInterface $messageBus) @@ -19,7 +21,7 @@ public function dispatch(BugInterface $bug): int { $steps = $bug->getSteps(); - if (count($steps) <= 2) { + if (count($steps) < $this->minSteps()) { return 0; } @@ -39,4 +41,6 @@ protected function maxPairs(array $steps): int { return ceil(sqrt(count($steps))); } + + abstract protected function minSteps(): int; } diff --git a/src/Reducer/Random/RandomDispatcher.php b/src/Reducer/Random/RandomDispatcher.php index c7ed50a9..995bb262 100644 --- a/src/Reducer/Random/RandomDispatcher.php +++ b/src/Reducer/Random/RandomDispatcher.php @@ -14,11 +14,21 @@ protected function getPairs(array $steps): array while (count($pairs) < $maxPairs) { $pair = array_rand(range(0, $length - 1), 2); - if (!in_array($pair, $pairs)) { + if ($pair[1] - $pair[0] >= static::MIN_PAIR_LENGTH && !in_array($pair, $pairs)) { $pairs[] = $pair; } } return $pairs; } + + protected function minSteps(): int + { + return 3; + } + + protected function maxPairs(array $steps): int + { + return count($steps) <= 3 ? 1 : parent::maxPairs($steps); + } } diff --git a/src/Reducer/Split/SplitDispatcher.php b/src/Reducer/Split/SplitDispatcher.php index c88029ad..ebfa9e11 100644 --- a/src/Reducer/Split/SplitDispatcher.php +++ b/src/Reducer/Split/SplitDispatcher.php @@ -12,14 +12,21 @@ protected function getPairs(array $steps): array $maxPairs = $this->maxPairs($steps); $pairs = []; - $range = range(0, $length - 1, ceil($length / $maxPairs)); + $range = range(0, $length - 1, (int) ceil($length / $maxPairs)); if (end($range) !== $length - 1) { $range[] = $length - 1; } for ($i = 0; $i < count($range) - 1; ++$i) { - $pairs[] = [$range[$i], $range[$i + 1]]; + if ($range[$i + 1] - $range[$i] >= static::MIN_PAIR_LENGTH) { + $pairs[] = [$range[$i], $range[$i + 1]]; + } } return $pairs; } + + protected function minSteps(): int + { + return 5; + } } diff --git a/tests/Reducer/DispatcherTestCase.php b/tests/Reducer/DispatcherTestCase.php index b0b6727e..075db5c5 100644 --- a/tests/Reducer/DispatcherTestCase.php +++ b/tests/Reducer/DispatcherTestCase.php @@ -25,54 +25,43 @@ protected function setUp(): void $this->bug = new Bug(); $this->bug->setId(123); $this->bug->setMessage('Something wrong'); - $this->bug->setSteps(array_map(fn () => $this->createMock(StepInterface::class), range(1, 11))); } - public function testDispatchTooShortSteps(): void - { - $this->bug->setSteps([$this->createMock(StepInterface::class)]); - $this->messageBus->expects($this->never())->method('dispatch'); - $this->assertSame(0, $this->dispatcher->dispatch($this->bug)); - $this->assertPairs(0); - } - - public function testDispatch(): void + /** + * @dataProvider stepsProvider + */ + public function testDispatch(int $length, array $expectedPairs): void { + if ($length > 0) { + $this->bug->setSteps(array_map(fn () => $this->createMock(StepInterface::class), range(0, $length - 1))); + } $this->messageBus - ->expects($this->exactly(4)) + ->expects($this->exactly(count($expectedPairs))) ->method('dispatch') - ->with($this->assertMessage()) + ->with($this->assertMessage($length)) ->willReturn(new Envelope(new \stdClass())); - $this->assertSame(4, $this->dispatcher->dispatch($this->bug)); - $this->assertPairs(); + $this->assertSame(count($expectedPairs), $this->dispatcher->dispatch($this->bug)); + $this->assertPairs($expectedPairs); } - protected function assertMessage(): Callback + protected function assertMessage(int $length): Callback { - return $this->callback(function ($message) { + return $this->callback(function ($message) use ($length) { if (!$message instanceof ReduceStepsMessage) { return false; } - if ( - $message->getBugId() !== $this->bug->getId() - || $message->getFrom() >= $message->getTo() - || 11 !== $message->getLength() - ) { - return false; - } $pair = [$message->getFrom(), $message->getTo()]; if (!in_array($pair, $this->pairs)) { $this->pairs[] = $pair; - - return true; } - return false; + return $message->getBugId() === $this->bug->getId() && + $message->getFrom() + 2 <= $message->getTo() && + $length === $message->getLength(); }); } - protected function assertPairs(int $count = 4): void - { - $this->assertCount($count, $this->pairs); - } + abstract protected function assertPairs(array $expectedPairs): void; + + abstract public function stepsProvider(): array; } diff --git a/tests/Reducer/Random/RandomDispatcherTest.php b/tests/Reducer/Random/RandomDispatcherTest.php index d132a36a..d5f83ab7 100644 --- a/tests/Reducer/Random/RandomDispatcherTest.php +++ b/tests/Reducer/Random/RandomDispatcherTest.php @@ -21,4 +21,30 @@ protected function setUp(): void parent::setUp(); $this->dispatcher = new RandomDispatcher($this->messageBus); } + + public function stepsProvider(): array + { + return [ + [0, []], + [1, []], + [2, []], + [3, [ + [0, 2], + ]], + [4, range(1, 2)], + [5, range(1, 3)], + [6, range(1, 3)], + [9, range(1, 3)], + [11, range(1, 4)], + ]; + } + + protected function assertPairs(array $expectedPairs): void + { + if (count($expectedPairs) > 1) { + $this->assertCount(count($expectedPairs), $this->pairs); + } else { + $this->assertSame($expectedPairs, $this->pairs); + } + } } diff --git a/tests/Reducer/Split/SplitDispatcherTest.php b/tests/Reducer/Split/SplitDispatcherTest.php index 54ab7f40..54ff22b9 100644 --- a/tests/Reducer/Split/SplitDispatcherTest.php +++ b/tests/Reducer/Split/SplitDispatcherTest.php @@ -22,16 +22,56 @@ protected function setUp(): void $this->dispatcher = new SplitDispatcher($this->messageBus); } - protected function assertPairs(int $count = 4): void + public function stepsProvider(): array { - parent::assertPairs($count); - if (4 === $count) { - $this->assertSame([ + return [ + [0, []], + [1, []], + [2, []], + [3, []], + [4, []], + [5, [ + [0, 2], + [2, 4], + ]], + [6, [ + [0, 2], + [2, 4], + ]], + [7, [ + [0, 3], + [3, 6], + ]], + [8, [ + [0, 3], + [3, 6], + ]], + [9, [ + [0, 3], + [3, 6], + [6, 8], + ]], + [10, [ [0, 3], [3, 6], [6, 9], - [9, 10], - ], $this->pairs); - } + ]], + [11, [ + [0, 3], + [3, 6], + [6, 9], + ]], + [12, [ + [0, 3], + [3, 6], + [6, 9], + [9, 11], + ]], + ]; + } + + protected function assertPairs(array $expectedPairs): void + { + $this->assertSame($expectedPairs, $this->pairs); } } From e8efdee1f7f5f34a78fda8c14708e163ccd263b0 Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 18 Apr 2022 22:37:12 +0700 Subject: [PATCH 39/85] Prevent updating steps after recording --- src/Repository/BugRepository.php | 13 +++++++++++++ src/Repository/BugRepositoryInterface.php | 2 ++ src/Service/Bug/BugHelper.php | 3 ++- tests/Repository/BugRepositoryTest.php | 20 +++++++++++++++++--- tests/Service/Bug/BugHelperTest.php | 16 +++++++++++----- 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/Repository/BugRepository.php b/src/Repository/BugRepository.php index 4acfb889..dee6d7ca 100644 --- a/src/Repository/BugRepository.php +++ b/src/Repository/BugRepository.php @@ -54,15 +54,28 @@ public function increaseTotal(BugInterface $bug, int $total): void public function startRecording(BugInterface $bug): void { + $this->getEntityManager()->refresh($bug); $bug->getVideo()->setRecording(true); $this->getEntityManager()->flush(); } public function stopRecording(BugInterface $bug): void { + // Recording bug may take long time. Reconnect to flush changes. + $this->getEntityManager()->getConnection()->connect(); + // Refresh so we don't update other fields while recording. + $this->getEntityManager()->refresh($bug); $bug->getVideo()->setRecording(false); + $this->getEntityManager()->flush(); + } + + public function updateVideoErrorMessage(BugInterface $bug, ?string $errorMessage): void + { // Recording bug may take long time. Reconnect to flush changes. $this->getEntityManager()->getConnection()->connect(); + // Refresh so we don't update other fields while recording. + $this->getEntityManager()->refresh($bug); + $bug->getVideo()->setErrorMessage($errorMessage); $this->getEntityManager()->flush(); } } diff --git a/src/Repository/BugRepositoryInterface.php b/src/Repository/BugRepositoryInterface.php index f834c7c8..6e074a55 100644 --- a/src/Repository/BugRepositoryInterface.php +++ b/src/Repository/BugRepositoryInterface.php @@ -16,4 +16,6 @@ public function increaseTotal(BugInterface $bug, int $total): void; public function startRecording(BugInterface $bug): void; public function stopRecording(BugInterface $bug): void; + + public function updateVideoErrorMessage(BugInterface $bug, ?string $errorMessage): void; } diff --git a/src/Service/Bug/BugHelper.php b/src/Service/Bug/BugHelper.php index 9dd6ed10..3d9250e3 100644 --- a/src/Service/Bug/BugHelper.php +++ b/src/Service/Bug/BugHelper.php @@ -96,7 +96,8 @@ public function recordVideo(int $bugId): void $this->bugRepository->startRecording($bug); $bug->setDebug(true); $this->stepsRunner->run($bug->getSteps(), $bug, function (Throwable $throwable) use ($bug) { - $bug->getVideo()->setErrorMessage( + $this->bugRepository->updateVideoErrorMessage( + $bug, $throwable->getMessage() !== $bug->getMessage() ? $throwable->getMessage() : null ); }); diff --git a/tests/Repository/BugRepositoryTest.php b/tests/Repository/BugRepositoryTest.php index 32aa9766..413986e3 100644 --- a/tests/Repository/BugRepositoryTest.php +++ b/tests/Repository/BugRepositoryTest.php @@ -28,6 +28,7 @@ class BugRepositoryTest extends TestCase protected EntityManagerDecorator $manager; protected BugInterface $bug; protected BugRepositoryInterface $bugRepository; + protected Connection $connection; protected function setUp(): void { @@ -54,6 +55,7 @@ protected function setUp(): void $this->createMock(StepInterface::class), $this->createMock(StepInterface::class), ]); + $this->connection = $this->createMock(Connection::class); } public function testUpdateSteps(): void @@ -156,6 +158,7 @@ public function testIncreaseTotal(): void public function testStartRecordingBug(): void { $this->bug->getVideo()->setRecording(false); + $this->manager->expects($this->once())->method('refresh')->with($this->bug); $this->manager->expects($this->once())->method('flush'); $this->bugRepository->startRecording($this->bug); $this->assertTrue($this->bug->getVideo()->isRecording()); @@ -163,12 +166,23 @@ public function testStartRecordingBug(): void public function testStopRecordingBug(): void { - $connection = $this->createMock(Connection::class); - $connection->expects($this->once())->method('connect'); + $this->connection->expects($this->once())->method('connect'); $this->bug->getVideo()->setRecording(true); + $this->manager->expects($this->once())->method('refresh')->with($this->bug); $this->manager->expects($this->once())->method('flush'); - $this->manager->expects($this->once())->method('getConnection')->willReturn($connection); + $this->manager->expects($this->once())->method('getConnection')->willReturn($this->connection); $this->bugRepository->stopRecording($this->bug); $this->assertFalse($this->bug->getVideo()->isRecording()); } + + public function testUpdateVideoErrorMessage(): void + { + $this->connection->expects($this->once())->method('connect'); + $this->bug->getVideo()->setErrorMessage(null); + $this->manager->expects($this->once())->method('refresh')->with($this->bug); + $this->manager->expects($this->once())->method('flush'); + $this->manager->expects($this->once())->method('getConnection')->willReturn($this->connection); + $this->bugRepository->updateVideoErrorMessage($this->bug, 'New error'); + $this->assertSame('New error', $this->bug->getVideo()->getErrorMessage()); + } } diff --git a/tests/Service/Bug/BugHelperTest.php b/tests/Service/Bug/BugHelperTest.php index ffff182d..e3fe7c93 100644 --- a/tests/Service/Bug/BugHelperTest.php +++ b/tests/Service/Bug/BugHelperTest.php @@ -256,13 +256,19 @@ public function testRecordVideo(?Throwable $exception): void $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn($this->bug); $this->bugRepository->expects($this->once())->method('startRecording')->with($this->bug); $this->bugRepository->expects($this->once())->method('stopRecording')->with($this->bug); - $this->helper->recordVideo(123); - $this->assertTrue($this->bug->isDebug()); - if ($exception && $exception->getMessage() !== $this->bug->getMessage()) { - $this->assertSame($exception->getMessage(), $this->bug->getVideo()->getErrorMessage()); + if ($exception) { + $this->bugRepository + ->expects($this->once()) + ->method('updateVideoErrorMessage') + ->with( + $this->bug, + $exception->getMessage() !== $this->bug->getMessage() ? $exception->getMessage() : null + ); } else { - $this->assertNull($this->bug->getVideo()->getErrorMessage()); + $this->bugRepository->expects($this->never())->method('updateVideoErrorMessage'); } + $this->helper->recordVideo(123); + $this->assertTrue($this->bug->isDebug()); } public function exceptionProvider(): array From a1438b49774b56c9bd82ed205b7e2322a6131cbe Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 24 Apr 2022 02:08:35 +0700 Subject: [PATCH 40/85] Prevent updating bug's steps after running --- src/Reducer/HandlerTemplate.php | 20 +++++--- src/Repository/BugRepository.php | 12 +---- src/Repository/BugRepositoryInterface.php | 2 - src/Resources/config/services.php | 8 +++ src/Service/Bug/BugHelper.php | 19 +++++--- src/Service/Step/Runner/BugStepsRunner.php | 28 ----------- src/Service/Step/StepHelper.php | 27 ++++++++++ src/Service/Step/StepHelperInterface.php | 8 +++ tests/Reducer/HandlerTestCase.php | 8 +++ tests/Reducer/Random/RandomHandlerTest.php | 3 +- tests/Reducer/Split/SplitHandlerTest.php | 3 +- tests/Repository/BugRepositoryTest.php | 57 +++++++--------------- tests/Service/Bug/BugHelperTest.php | 31 ++++++------ tests/Service/Step/StepHelperTest.php | 42 ++++++++++++++++ 14 files changed, 157 insertions(+), 111 deletions(-) create mode 100644 src/Service/Step/StepHelper.php create mode 100644 src/Service/Step/StepHelperInterface.php create mode 100644 tests/Service/Step/StepHelperTest.php diff --git a/src/Reducer/HandlerTemplate.php b/src/Reducer/HandlerTemplate.php index 8b8bb30b..0cc070ea 100644 --- a/src/Reducer/HandlerTemplate.php +++ b/src/Reducer/HandlerTemplate.php @@ -9,6 +9,7 @@ use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Builder\StepsBuilderInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\StepHelperInterface; abstract class HandlerTemplate implements HandlerInterface { @@ -16,17 +17,20 @@ abstract class HandlerTemplate implements HandlerInterface protected MessageBusInterface $messageBus; protected BugStepsRunner $stepsRunner; protected StepsBuilderInterface $stepsBuilder; + protected StepHelperInterface $stepHelper; public function __construct( BugRepositoryInterface $bugRepository, MessageBusInterface $messageBus, BugStepsRunner $stepsRunner, - StepsBuilderInterface $stepsBuilder + StepsBuilderInterface $stepsBuilder, + StepHelperInterface $stepHelper ) { $this->bugRepository = $bugRepository; $this->messageBus = $messageBus; $this->stepsRunner = $stepsRunner; $this->stepsBuilder = $stepsBuilder; + $this->stepHelper = $stepHelper; } public function handle(BugInterface $bug, int $from, int $to): void @@ -37,11 +41,15 @@ public function handle(BugInterface $bug, int $from, int $to): void } $bug->setDebug(false); - $this->stepsRunner->run($newSteps, $bug, function (Throwable $throwable) use ($bug, $newSteps): void { - if ($throwable->getMessage() === $bug->getMessage()) { - $this->bugRepository->updateSteps($bug, $newSteps); - $this->messageBus->dispatch(new ReduceBugMessage($bug->getId())); + $this->stepsRunner->run( + $this->stepHelper->cloneStepsAndResetColor($newSteps), + $bug, + function (Throwable $throwable) use ($bug, $newSteps): void { + if ($throwable->getMessage() === $bug->getMessage()) { + $this->bugRepository->updateSteps($bug, $newSteps); + $this->messageBus->dispatch(new ReduceBugMessage($bug->getId())); + } } - }); + ); } } diff --git a/src/Repository/BugRepository.php b/src/Repository/BugRepository.php index dee6d7ca..fac7f7f6 100644 --- a/src/Repository/BugRepository.php +++ b/src/Repository/BugRepository.php @@ -21,7 +21,7 @@ public function updateSteps(BugInterface $bug, array $newSteps): void // Refresh the bug for the latest steps's length. $this->getEntityManager()->refresh($bug); - if (count($newSteps) <= count($bug->getSteps())) { + if (count($newSteps) < count($bug->getSteps())) { $this->getEntityManager()->lock($bug, LockMode::PESSIMISTIC_WRITE); $bug->getProgress()->setTotal(0); $bug->getProgress()->setProcessed(0); @@ -68,14 +68,4 @@ public function stopRecording(BugInterface $bug): void $bug->getVideo()->setRecording(false); $this->getEntityManager()->flush(); } - - public function updateVideoErrorMessage(BugInterface $bug, ?string $errorMessage): void - { - // Recording bug may take long time. Reconnect to flush changes. - $this->getEntityManager()->getConnection()->connect(); - // Refresh so we don't update other fields while recording. - $this->getEntityManager()->refresh($bug); - $bug->getVideo()->setErrorMessage($errorMessage); - $this->getEntityManager()->flush(); - } } diff --git a/src/Repository/BugRepositoryInterface.php b/src/Repository/BugRepositoryInterface.php index 6e074a55..f834c7c8 100644 --- a/src/Repository/BugRepositoryInterface.php +++ b/src/Repository/BugRepositoryInterface.php @@ -16,6 +16,4 @@ public function increaseTotal(BugInterface $bug, int $total): void; public function startRecording(BugInterface $bug): void; public function stopRecording(BugInterface $bug): void; - - public function updateVideoErrorMessage(BugInterface $bug, ?string $errorMessage): void; } diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index 0ff91385..f28e853a 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -68,6 +68,8 @@ use Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepRunner; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepRunnerInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\TaskStepsRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\StepHelper; +use Tienvx\Bundle\MbtBundle\Service\Step\StepHelperInterface; use Tienvx\Bundle\MbtBundle\Service\Task\TaskHelper; use Tienvx\Bundle\MbtBundle\Service\Task\TaskHelperInterface; use Tienvx\Bundle\MbtBundle\Validator\TagsValidator; @@ -136,6 +138,7 @@ service(MessageBusInterface::class), service(BugStepsRunner::class), service(StepsBuilderInterface::class), + service(StepHelperInterface::class), ]) ->set(RandomReducer::class) ->args([ @@ -153,6 +156,7 @@ service(MessageBusInterface::class), service(BugStepsRunner::class), service(StepsBuilderInterface::class), + service(StepHelperInterface::class), ]) ->set(SplitReducer::class) ->args([ @@ -230,6 +234,7 @@ service(BugRepositoryInterface::class), service(MessageBusInterface::class), service(BugNotifierInterface::class), + service(StepHelperInterface::class), service(BugStepsRunner::class), service(ConfigInterface::class), ]) @@ -258,6 +263,9 @@ ]) ->alias(PetrinetDomainLogicInterface::class, PetrinetDomainLogic::class) + ->set(StepHelperInterface::class) + ->alias(StepHelperInterface::class, StepHelper::class) + ->set(StepRunner::class) ->args([ service(CommandRunnerManagerInterface::class), diff --git a/src/Service/Bug/BugHelper.php b/src/Service/Bug/BugHelper.php index 3d9250e3..24f3ddd7 100644 --- a/src/Service/Bug/BugHelper.php +++ b/src/Service/Bug/BugHelper.php @@ -14,6 +14,7 @@ use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\StepHelperInterface; class BugHelper implements BugHelperInterface { @@ -21,6 +22,7 @@ class BugHelper implements BugHelperInterface protected BugRepositoryInterface $bugRepository; protected MessageBusInterface $messageBus; protected BugNotifierInterface $bugNotifier; + protected StepHelperInterface $stepHelper; protected BugStepsRunner $stepsRunner; protected ConfigInterface $config; @@ -29,6 +31,7 @@ public function __construct( BugRepositoryInterface $bugRepository, MessageBusInterface $messageBus, BugNotifierInterface $bugNotifier, + StepHelperInterface $stepHelper, BugStepsRunner $stepsRunner, ConfigInterface $config ) { @@ -36,6 +39,7 @@ public function __construct( $this->bugRepository = $bugRepository; $this->messageBus = $messageBus; $this->bugNotifier = $bugNotifier; + $this->stepHelper = $stepHelper; $this->stepsRunner = $stepsRunner; $this->config = $config; } @@ -95,12 +99,15 @@ public function recordVideo(int $bugId): void $this->bugRepository->startRecording($bug); $bug->setDebug(true); - $this->stepsRunner->run($bug->getSteps(), $bug, function (Throwable $throwable) use ($bug) { - $this->bugRepository->updateVideoErrorMessage( - $bug, - $throwable->getMessage() !== $bug->getMessage() ? $throwable->getMessage() : null - ); - }); + $this->stepsRunner->run( + $this->stepHelper->cloneStepsAndResetColor($bug->getSteps()), + $bug, + function (Throwable $throwable) use ($bug) { + $bug->getVideo()->setErrorMessage( + $throwable->getMessage() !== $bug->getMessage() ? $throwable->getMessage() : null + ); + } + ); $this->bugRepository->stopRecording($bug); } diff --git a/src/Service/Step/Runner/BugStepsRunner.php b/src/Service/Step/Runner/BugStepsRunner.php index 7f2e4288..3635dabb 100644 --- a/src/Service/Step/Runner/BugStepsRunner.php +++ b/src/Service/Step/Runner/BugStepsRunner.php @@ -2,39 +2,11 @@ namespace Tienvx\Bundle\MbtBundle\Service\Step\Runner; -use Facebook\WebDriver\Remote\RemoteWebDriver; -use SingleColorPetrinet\Model\Color; -use SingleColorPetrinet\Model\ColorInterface; use Throwable; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; -use Tienvx\Bundle\MbtBundle\Model\DebugInterface; -use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; class BugStepsRunner extends StepsRunner { - protected ?ColorInterface $lastColor; - - protected function start(DebugInterface $entity): RemoteWebDriver - { - $this->lastColor = new Color(); - - return parent::start($entity); - } - - protected function stop(?RemoteWebDriver $driver): void - { - parent::stop($driver); - $this->lastColor = null; - } - - protected function runStep(StepInterface $step, RevisionInterface $revision, RemoteWebDriver $driver): void - { - $color = clone $step->getColor(); - $step->setColor($this->lastColor); - parent::runStep($step, $revision, $driver); - $this->lastColor = $color; - } - protected function catchException(callable $handleException, Throwable $throwable, ?StepInterface $step): void { $handleException($throwable); diff --git a/src/Service/Step/StepHelper.php b/src/Service/Step/StepHelper.php new file mode 100644 index 00000000..c13a2b87 --- /dev/null +++ b/src/Service/Step/StepHelper.php @@ -0,0 +1,27 @@ +setColor($lastColor ?? new Color()); + $newSteps[] = $newStep; + $lastColor = clone $step->getColor(); + } + + return $newSteps; + } +} diff --git a/src/Service/Step/StepHelperInterface.php b/src/Service/Step/StepHelperInterface.php new file mode 100644 index 00000000..88d4cd73 --- /dev/null +++ b/src/Service/Step/StepHelperInterface.php @@ -0,0 +1,8 @@ +messageBus = $this->createMock(MessageBusInterface::class); $this->stepsRunner = $this->createMock(BugStepsRunner::class); $this->stepsBuilder = $this->createMock(StepsBuilderInterface::class); + $this->stepHelper = $this->createMock(StepHelperInterface::class); $this->newSteps = [ $this->createMock(StepInterface::class), $this->createMock(StepInterface::class), @@ -73,6 +76,11 @@ public function testHandleOldBug(): void */ public function testHandle(?Throwable $exception, bool $updateSteps): void { + $this->stepHelper + ->expects($this->once()) + ->method('cloneStepsAndResetColor') + ->with($this->newSteps) + ->willReturnArgument(0); $this->stepsRunner->expects($this->once()) ->method('run') ->with( diff --git a/tests/Reducer/Random/RandomHandlerTest.php b/tests/Reducer/Random/RandomHandlerTest.php index 3a1b866a..acd7d3eb 100644 --- a/tests/Reducer/Random/RandomHandlerTest.php +++ b/tests/Reducer/Random/RandomHandlerTest.php @@ -27,7 +27,8 @@ protected function setUp(): void $this->bugRepository, $this->messageBus, $this->stepsRunner, - $this->stepsBuilder + $this->stepsBuilder, + $this->stepHelper ); } } diff --git a/tests/Reducer/Split/SplitHandlerTest.php b/tests/Reducer/Split/SplitHandlerTest.php index 63515131..878bf5e2 100644 --- a/tests/Reducer/Split/SplitHandlerTest.php +++ b/tests/Reducer/Split/SplitHandlerTest.php @@ -27,7 +27,8 @@ protected function setUp(): void $this->bugRepository, $this->messageBus, $this->stepsRunner, - $this->stepsBuilder + $this->stepsBuilder, + $this->stepHelper ); } } diff --git a/tests/Repository/BugRepositoryTest.php b/tests/Repository/BugRepositoryTest.php index 413986e3..6be72ba0 100644 --- a/tests/Repository/BugRepositoryTest.php +++ b/tests/Repository/BugRepositoryTest.php @@ -58,16 +58,17 @@ protected function setUp(): void $this->connection = $this->createMock(Connection::class); } - public function testUpdateSteps(): void + /** + * @dataProvider stepsProvider + */ + public function testUpdateSteps(int $length, int $expectedLength, int $expectedProcessed, int $expectedTotal): void { - $newSteps = [ - $this->createMock(StepInterface::class), - $this->createMock(StepInterface::class), - $this->createMock(StepInterface::class), - $this->createMock(StepInterface::class), - ]; + $newSteps = array_map(fn () => $this->createMock(StepInterface::class), range(0, $length - 1)); $this->manager->expects($this->once())->method('refresh')->with($this->bug); - $this->manager->expects($this->never())->method('lock'); + $this->manager + ->expects($this->exactly(count($this->bug->getSteps()) !== $expectedLength)) + ->method('lock') + ->with($this->bug, LockMode::PESSIMISTIC_WRITE); $this->manager ->expects($this->once()) ->method('wrapInTransaction') @@ -77,31 +78,18 @@ public function testUpdateSteps(): void return true; })); $this->bugRepository->updateSteps($this->bug, $newSteps); - $this->assertNotSame($newSteps, $this->bug->getSteps()); - $this->assertSame(5, $this->bug->getProgress()->getProcessed()); - $this->assertSame(10, $this->bug->getProgress()->getTotal()); + $this->assertCount($expectedLength, $this->bug->getSteps()); + $this->assertSame($expectedProcessed, $this->bug->getProgress()->getProcessed()); + $this->assertSame($expectedTotal, $this->bug->getProgress()->getTotal()); } - public function testUpdateStepsWithShorterSteps(): void + public function stepsProvider(): array { - $newSteps = [ - $this->createMock(StepInterface::class), - $this->createMock(StepInterface::class), + return [ + [4, 3, 5, 10], + [3, 3, 5, 10], + [2, 2, 0, 0], ]; - $this->manager->expects($this->once())->method('refresh')->with($this->bug); - $this->manager->expects($this->once())->method('lock')->with($this->bug, LockMode::PESSIMISTIC_WRITE); - $this->manager - ->expects($this->once()) - ->method('wrapInTransaction') - ->with($this->callback(function ($callback) { - $callback(); - - return true; - })); - $this->bugRepository->updateSteps($this->bug, $newSteps); - $this->assertSame($newSteps, $this->bug->getSteps()); - $this->assertSame(0, $this->bug->getProgress()->getProcessed()); - $this->assertSame(0, $this->bug->getProgress()->getTotal()); } public function testIncreaseProcessed(): void @@ -174,15 +162,4 @@ public function testStopRecordingBug(): void $this->bugRepository->stopRecording($this->bug); $this->assertFalse($this->bug->getVideo()->isRecording()); } - - public function testUpdateVideoErrorMessage(): void - { - $this->connection->expects($this->once())->method('connect'); - $this->bug->getVideo()->setErrorMessage(null); - $this->manager->expects($this->once())->method('refresh')->with($this->bug); - $this->manager->expects($this->once())->method('flush'); - $this->manager->expects($this->once())->method('getConnection')->willReturn($this->connection); - $this->bugRepository->updateVideoErrorMessage($this->bug, 'New error'); - $this->assertSame('New error', $this->bug->getVideo()->getErrorMessage()); - } } diff --git a/tests/Service/Bug/BugHelperTest.php b/tests/Service/Bug/BugHelperTest.php index e3fe7c93..5efd72b3 100644 --- a/tests/Service/Bug/BugHelperTest.php +++ b/tests/Service/Bug/BugHelperTest.php @@ -24,6 +24,7 @@ use Tienvx\Bundle\MbtBundle\Service\Bug\BugNotifierInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\StepHelperInterface; /** * @covers \Tienvx\Bundle\MbtBundle\Service\Bug\BugHelper @@ -45,6 +46,7 @@ class BugHelperTest extends TestCase protected BugRepositoryInterface $bugRepository; protected MessageBusInterface $messageBus; protected BugNotifierInterface $bugNotifier; + protected StepHelperInterface $stepHelper; protected BugStepsRunner $stepsRunner; protected ConfigInterface $config; protected BugHelperInterface $helper; @@ -57,6 +59,7 @@ protected function setUp(): void $this->bugRepository = $this->createMock(BugRepositoryInterface::class); $this->messageBus = $this->createMock(MessageBusInterface::class); $this->bugNotifier = $this->createMock(BugNotifierInterface::class); + $this->stepHelper = $this->createMock(StepHelperInterface::class); $this->stepsRunner = $this->createMock(BugStepsRunner::class); $this->config = $this->createMock(ConfigInterface::class); $this->helper = new BugHelper( @@ -64,6 +67,7 @@ protected function setUp(): void $this->bugRepository, $this->messageBus, $this->bugNotifier, + $this->stepHelper, $this->stepsRunner, $this->config ); @@ -225,7 +229,7 @@ public function testRecordVideoMissingBug(): void $this->helper->recordVideo(123); } - public function testRunTaskAlreadyRunning(): void + public function testRecordVideoAlreadyRecording(): void { $this->expectException(RecoverableMessageHandlingException::class); $this->expectExceptionMessage('Can not record video for bug 123: bug is recording. Will retry later'); @@ -237,7 +241,7 @@ public function testRunTaskAlreadyRunning(): void /** * @dataProvider exceptionProvider */ - public function testRecordVideo(?Throwable $exception): void + public function testRecordVideo(?Throwable $exception, ?string $expectedVideoErrorMessage): void { $this->stepsRunner ->expects($this->once()) @@ -254,29 +258,24 @@ public function testRecordVideo(?Throwable $exception): void }) ); $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn($this->bug); + $this->stepHelper + ->expects($this->once()) + ->method('cloneStepsAndResetColor') + ->with($this->bug->getSteps()) + ->willReturnArgument(0); $this->bugRepository->expects($this->once())->method('startRecording')->with($this->bug); $this->bugRepository->expects($this->once())->method('stopRecording')->with($this->bug); - if ($exception) { - $this->bugRepository - ->expects($this->once()) - ->method('updateVideoErrorMessage') - ->with( - $this->bug, - $exception->getMessage() !== $this->bug->getMessage() ? $exception->getMessage() : null - ); - } else { - $this->bugRepository->expects($this->never())->method('updateVideoErrorMessage'); - } $this->helper->recordVideo(123); $this->assertTrue($this->bug->isDebug()); + $this->assertSame($expectedVideoErrorMessage, $this->bug->getVideo()->getErrorMessage()); } public function exceptionProvider(): array { return [ - [null], - [new Exception('Something wrong')], - [new Exception('Something else wrong')], + [null, null], + [new Exception('Something wrong'), null], + [new Exception('Something else wrong'), 'Something else wrong'], ]; } } diff --git a/tests/Service/Step/StepHelperTest.php b/tests/Service/Step/StepHelperTest.php new file mode 100644 index 00000000..131a9a19 --- /dev/null +++ b/tests/Service/Step/StepHelperTest.php @@ -0,0 +1,42 @@ +cloneStepsAndResetColor($steps); + $this->assertCount(count($steps), $newSteps); + foreach ($newSteps as $index => $newStep) { + $this->assertInstanceOf(StepInterface::class, $newStep); + $this->assertSame($steps[$index]->getPlaces(), $newStep->getPlaces()); + $this->assertSame($steps[$index]->getTransition(), $newStep->getTransition()); + $this->assertNotSame($steps[$index]->getColor(), $newStep->getColor()); + if ($index > 0) { + $this->assertNotSame($steps[$index - 1]->getColor(), $newStep->getColor()); + $this->assertSame($steps[$index - 1]->getColor()->getValues(), $newStep->getColor()->getValues()); + } else { + $this->assertSame([], $newStep->getColor()->getValues()); + } + } + } +} From acc68e4858bf4500d6d2de0c4c50566c61b6e6e0 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 24 Apr 2022 02:14:00 +0700 Subject: [PATCH 41/85] Test clone invalid steps --- tests/Service/Step/StepHelperTest.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/Service/Step/StepHelperTest.php b/tests/Service/Step/StepHelperTest.php index 131a9a19..41b4abe6 100644 --- a/tests/Service/Step/StepHelperTest.php +++ b/tests/Service/Step/StepHelperTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\TestCase; use SingleColorPetrinet\Model\Color; +use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; use Tienvx\Bundle\MbtBundle\Service\Step\StepHelper; use Tienvx\Bundle\MbtBundle\ValueObject\Bug\Step; @@ -15,6 +16,21 @@ */ class StepHelperTest extends TestCase { + protected StepHelper $stepHelper; + + protected function setUp(): void + { + $this->stepHelper = new StepHelper(); + } + + public function testCloneInvalidSteps(): void + { + $this->expectExceptionObject( + new UnexpectedValueException(sprintf('Step must be instance of "%s".', StepInterface::class)) + ); + $this->stepHelper->cloneStepsAndResetColor([new \stdClass()]); + } + public function testCloneStepsAndResetColor(): void { $steps = [ @@ -23,8 +39,7 @@ public function testCloneStepsAndResetColor(): void new Step([2], new Color(), 2), new Step([3], new Color(), 3), ]; - $stepHelper = new StepHelper(); - $newSteps = $stepHelper->cloneStepsAndResetColor($steps); + $newSteps = $this->stepHelper->cloneStepsAndResetColor($steps); $this->assertCount(count($steps), $newSteps); foreach ($newSteps as $index => $newStep) { $this->assertInstanceOf(StepInterface::class, $newStep); From 0aba8c84fc3c9a0dc6179a84ef96d69dd54fa051 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 24 Apr 2022 02:22:16 +0700 Subject: [PATCH 42/85] Fix service id --- src/Resources/config/services.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index f28e853a..7c6a907e 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -263,7 +263,7 @@ ]) ->alias(PetrinetDomainLogicInterface::class, PetrinetDomainLogic::class) - ->set(StepHelperInterface::class) + ->set(StepHelper::class) ->alias(StepHelperInterface::class, StepHelper::class) ->set(StepRunner::class) From f4520c86f89f5e3324ff78098d317c8b24965672 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 24 Apr 2022 16:25:58 +0700 Subject: [PATCH 43/85] Check can run step while reducing steps --- src/Reducer/HandlerTemplate.php | 8 +- src/Resources/config/services.php | 28 ++-- src/Service/Bug/BugHelper.php | 8 +- src/Service/Step/Runner/BugStepsRunner.php | 2 +- ...StepsRunner.php => ExploreStepsRunner.php} | 8 +- src/Service/Step/Runner/RecordStepsRunner.php | 7 + src/Service/Step/Runner/ReduceStepsRunner.php | 58 +++++++++ src/Service/Step/Runner/StepsRunner.php | 10 +- src/Service/Step/StepHelper.php | 18 ++- src/Service/Step/StepHelperInterface.php | 4 +- src/Service/Task/TaskHelper.php | 6 +- tests/Reducer/HandlerTestCase.php | 17 ++- tests/Service/Bug/BugHelperTest.php | 17 ++- .../Step/Runner/BugStepsRunnerTestCase.php | 16 +++ ...nerTest.php => ExploreStepsRunnerTest.php} | 8 +- ...nnerTest.php => RecordStepsRunnerTest.php} | 16 +-- .../Step/Runner/ReduceStepsRunnerTest.php | 120 ++++++++++++++++++ tests/Service/Step/StepHelperTest.php | 21 ++- tests/Service/Task/TaskHelperTest.php | 6 +- 19 files changed, 311 insertions(+), 67 deletions(-) rename src/Service/Step/Runner/{TaskStepsRunner.php => ExploreStepsRunner.php} (83%) create mode 100644 src/Service/Step/Runner/RecordStepsRunner.php create mode 100644 src/Service/Step/Runner/ReduceStepsRunner.php create mode 100644 tests/Service/Step/Runner/BugStepsRunnerTestCase.php rename tests/Service/Step/Runner/{TaskStepsRunnerTest.php => ExploreStepsRunnerTest.php} (89%) rename tests/Service/Step/Runner/{BugStepsRunnerTest.php => RecordStepsRunnerTest.php} (57%) create mode 100644 tests/Service/Step/Runner/ReduceStepsRunnerTest.php diff --git a/src/Reducer/HandlerTemplate.php b/src/Reducer/HandlerTemplate.php index 0cc070ea..8e939d06 100644 --- a/src/Reducer/HandlerTemplate.php +++ b/src/Reducer/HandlerTemplate.php @@ -8,21 +8,21 @@ use Tienvx\Bundle\MbtBundle\Model\BugInterface; use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Builder\StepsBuilderInterface; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\ReduceStepsRunner; use Tienvx\Bundle\MbtBundle\Service\Step\StepHelperInterface; abstract class HandlerTemplate implements HandlerInterface { protected BugRepositoryInterface $bugRepository; protected MessageBusInterface $messageBus; - protected BugStepsRunner $stepsRunner; + protected ReduceStepsRunner $stepsRunner; protected StepsBuilderInterface $stepsBuilder; protected StepHelperInterface $stepHelper; public function __construct( BugRepositoryInterface $bugRepository, MessageBusInterface $messageBus, - BugStepsRunner $stepsRunner, + ReduceStepsRunner $stepsRunner, StepsBuilderInterface $stepsBuilder, StepHelperInterface $stepHelper ) { @@ -42,7 +42,7 @@ public function handle(BugInterface $bug, int $from, int $to): void $bug->setDebug(false); $this->stepsRunner->run( - $this->stepHelper->cloneStepsAndResetColor($newSteps), + $this->stepHelper->cloneAndResetSteps($newSteps, $bug->getTask()->getModelRevision()), $bug, function (Throwable $throwable) use ($bug, $newSteps): void { if ($throwable->getMessage() === $bug->getMessage()) { diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index 7c6a907e..cee10a39 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -64,10 +64,11 @@ use Tienvx\Bundle\MbtBundle\Service\SelenoidHelperInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Builder\ShortestPathStepsBuilder; use Tienvx\Bundle\MbtBundle\Service\Step\Builder\StepsBuilderInterface; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\ExploreStepsRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\RecordStepsRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\ReduceStepsRunner; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepRunner; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepRunnerInterface; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\TaskStepsRunner; use Tienvx\Bundle\MbtBundle\Service\Step\StepHelper; use Tienvx\Bundle\MbtBundle\Service\Step\StepHelperInterface; use Tienvx\Bundle\MbtBundle\Service\Task\TaskHelper; @@ -136,7 +137,7 @@ ->args([ service(BugRepositoryInterface::class), service(MessageBusInterface::class), - service(BugStepsRunner::class), + service(ReduceStepsRunner::class), service(StepsBuilderInterface::class), service(StepHelperInterface::class), ]) @@ -154,7 +155,7 @@ ->args([ service(BugRepositoryInterface::class), service(MessageBusInterface::class), - service(BugStepsRunner::class), + service(ReduceStepsRunner::class), service(StepsBuilderInterface::class), service(StepHelperInterface::class), ]) @@ -235,7 +236,7 @@ service(MessageBusInterface::class), service(BugNotifierInterface::class), service(StepHelperInterface::class), - service(BugStepsRunner::class), + service(RecordStepsRunner::class), service(ConfigInterface::class), ]) ->alias(BugHelperInterface::class, BugHelper::class) @@ -244,7 +245,7 @@ ->args([ service(GeneratorManagerInterface::class), service(TaskRepositoryInterface::class), - service(TaskStepsRunner::class), + service(ExploreStepsRunner::class), service(ConfigInterface::class), ]) ->alias(TaskHelperInterface::class, TaskHelper::class) @@ -264,6 +265,9 @@ ->alias(PetrinetDomainLogicInterface::class, PetrinetDomainLogic::class) ->set(StepHelper::class) + ->args([ + service(ModelHelperInterface::class), + ]) ->alias(StepHelperInterface::class, StepHelper::class) ->set(StepRunner::class) @@ -272,13 +276,21 @@ ]) ->alias(StepRunnerInterface::class, StepRunner::class) - ->set(TaskStepsRunner::class) + ->set(ExploreStepsRunner::class) ->args([ service(SelenoidHelperInterface::class), service(StepRunnerInterface::class), service(ConfigInterface::class), ]) - ->set(BugStepsRunner::class) + ->set(ReduceStepsRunner::class) + ->args([ + service(SelenoidHelperInterface::class), + service(StepRunnerInterface::class), + service(PetrinetHelperInterface::class), + service(MarkingHelperInterface::class), + service(GuardedTransitionServiceInterface::class), + ]) + ->set(RecordStepsRunner::class) ->args([ service(SelenoidHelperInterface::class), service(StepRunnerInterface::class), diff --git a/src/Service/Bug/BugHelper.php b/src/Service/Bug/BugHelper.php index 24f3ddd7..a78df9f5 100644 --- a/src/Service/Bug/BugHelper.php +++ b/src/Service/Bug/BugHelper.php @@ -13,7 +13,7 @@ use Tienvx\Bundle\MbtBundle\Reducer\ReducerManagerInterface; use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\RecordStepsRunner; use Tienvx\Bundle\MbtBundle\Service\Step\StepHelperInterface; class BugHelper implements BugHelperInterface @@ -23,7 +23,7 @@ class BugHelper implements BugHelperInterface protected MessageBusInterface $messageBus; protected BugNotifierInterface $bugNotifier; protected StepHelperInterface $stepHelper; - protected BugStepsRunner $stepsRunner; + protected RecordStepsRunner $stepsRunner; protected ConfigInterface $config; public function __construct( @@ -32,7 +32,7 @@ public function __construct( MessageBusInterface $messageBus, BugNotifierInterface $bugNotifier, StepHelperInterface $stepHelper, - BugStepsRunner $stepsRunner, + RecordStepsRunner $stepsRunner, ConfigInterface $config ) { $this->reducerManager = $reducerManager; @@ -100,7 +100,7 @@ public function recordVideo(int $bugId): void $this->bugRepository->startRecording($bug); $bug->setDebug(true); $this->stepsRunner->run( - $this->stepHelper->cloneStepsAndResetColor($bug->getSteps()), + $this->stepHelper->cloneAndResetSteps($bug->getSteps(), $bug->getTask()->getModelRevision()), $bug, function (Throwable $throwable) use ($bug) { $bug->getVideo()->setErrorMessage( diff --git a/src/Service/Step/Runner/BugStepsRunner.php b/src/Service/Step/Runner/BugStepsRunner.php index 3635dabb..9a4a0336 100644 --- a/src/Service/Step/Runner/BugStepsRunner.php +++ b/src/Service/Step/Runner/BugStepsRunner.php @@ -5,7 +5,7 @@ use Throwable; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; -class BugStepsRunner extends StepsRunner +abstract class BugStepsRunner extends StepsRunner { protected function catchException(callable $handleException, Throwable $throwable, ?StepInterface $step): void { diff --git a/src/Service/Step/Runner/TaskStepsRunner.php b/src/Service/Step/Runner/ExploreStepsRunner.php similarity index 83% rename from src/Service/Step/Runner/TaskStepsRunner.php rename to src/Service/Step/Runner/ExploreStepsRunner.php index c72957bb..b4dfc22f 100644 --- a/src/Service/Step/Runner/TaskStepsRunner.php +++ b/src/Service/Step/Runner/ExploreStepsRunner.php @@ -8,10 +8,11 @@ use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; use Tienvx\Bundle\MbtBundle\Model\BugInterface; use Tienvx\Bundle\MbtBundle\Model\DebugInterface; +use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; use Tienvx\Bundle\MbtBundle\Service\SelenoidHelperInterface; -class TaskStepsRunner extends StepsRunner +class ExploreStepsRunner extends StepsRunner { protected array $steps; protected ConfigInterface $config; @@ -47,11 +48,12 @@ protected function stop(?RemoteWebDriver $driver): void $this->steps = []; } - protected function canStop(StepInterface $step): bool + protected function runStep(StepInterface $step, RevisionInterface $revision, RemoteWebDriver $driver): bool { + parent::runStep($step, $revision, $driver); $this->steps[] = clone $step; - return count($this->steps) >= $this->config->getMaxSteps(); + return count($this->steps) < $this->config->getMaxSteps(); } protected function createBug(array $steps, string $message): BugInterface diff --git a/src/Service/Step/Runner/RecordStepsRunner.php b/src/Service/Step/Runner/RecordStepsRunner.php new file mode 100644 index 00000000..2831906e --- /dev/null +++ b/src/Service/Step/Runner/RecordStepsRunner.php @@ -0,0 +1,7 @@ +petrinetHelper = $petrinetHelper; + $this->markingHelper = $markingHelper; + $this->transitionService = $transitionService; + } + + protected function start(DebugInterface $entity): RemoteWebDriver + { + $this->petrinet = $this->petrinetHelper->build($entity->getTask()->getModelRevision()); + + return parent::start($entity); + } + + protected function stop(?RemoteWebDriver $driver): void + { + parent::stop($driver); + $this->petrinet = null; + } + + protected function runStep(StepInterface $step, RevisionInterface $revision, RemoteWebDriver $driver): bool + { + $marking = $this->markingHelper->getMarking($this->petrinet, $step->getPlaces(), $step->getColor()); + $transition = $this->petrinet->getTransitions()[$step->getTransition()]; + if ($this->transitionService->isEnabled($transition, $marking)) { + return parent::runStep($step, $revision, $driver); + } + + return false; + } +} diff --git a/src/Service/Step/Runner/StepsRunner.php b/src/Service/Step/Runner/StepsRunner.php index 1b307711..c88d5d34 100644 --- a/src/Service/Step/Runner/StepsRunner.php +++ b/src/Service/Step/Runner/StepsRunner.php @@ -33,8 +33,7 @@ public function run(iterable $steps, DebugInterface $entity, callable $handleExc if (!$step instanceof StepInterface) { throw new UnexpectedValueException(sprintf('Step must be instance of "%s".', StepInterface::class)); } - $this->runStep($step, $entity->getTask()->getModelRevision(), $driver); - if ($this->canStop($step)) { + if (!$this->runStep($step, $entity->getTask()->getModelRevision(), $driver)) { break; } } @@ -52,14 +51,11 @@ protected function start(DebugInterface $entity): RemoteWebDriver return $this->selenoidHelper->createDriver($entity); } - protected function runStep(StepInterface $step, RevisionInterface $revision, RemoteWebDriver $driver): void + protected function runStep(StepInterface $step, RevisionInterface $revision, RemoteWebDriver $driver): bool { $this->stepRunner->run($step, $revision, $driver); - } - protected function canStop(StepInterface $step): bool - { - return false; + return true; } abstract protected function catchException( diff --git a/src/Service/Step/StepHelper.php b/src/Service/Step/StepHelper.php index c13a2b87..6d16b9db 100644 --- a/src/Service/Step/StepHelper.php +++ b/src/Service/Step/StepHelper.php @@ -5,21 +5,33 @@ use SingleColorPetrinet\Model\Color; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; +use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; +use Tienvx\Bundle\MbtBundle\Service\Model\ModelHelperInterface; class StepHelper implements StepHelperInterface { - public function cloneStepsAndResetColor(array $steps): array + protected ModelHelperInterface $modelHelper; + + public function __construct(ModelHelperInterface $modelHelper) + { + $this->modelHelper = $modelHelper; + } + + public function cloneAndResetSteps(array $steps, RevisionInterface $revision): array { - $lastColor = null; + $lastColor = new Color(); + $lastPlaces = $this->modelHelper->getStartPlaceIds($revision); $newSteps = []; foreach ($steps as $step) { if (!$step instanceof StepInterface) { throw new UnexpectedValueException(sprintf('Step must be instance of "%s".', StepInterface::class)); } $newStep = clone $step; - $newStep->setColor($lastColor ?? new Color()); + $newStep->setColor($lastColor); + $newStep->setPlaces($lastPlaces); $newSteps[] = $newStep; $lastColor = clone $step->getColor(); + $lastPlaces = $step->getPlaces(); } return $newSteps; diff --git a/src/Service/Step/StepHelperInterface.php b/src/Service/Step/StepHelperInterface.php index 88d4cd73..5d2f2411 100644 --- a/src/Service/Step/StepHelperInterface.php +++ b/src/Service/Step/StepHelperInterface.php @@ -2,7 +2,9 @@ namespace Tienvx\Bundle\MbtBundle\Service\Step; +use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; + interface StepHelperInterface { - public function cloneStepsAndResetColor(array $steps): array; + public function cloneAndResetSteps(array $steps, RevisionInterface $revision): array; } diff --git a/src/Service/Task/TaskHelper.php b/src/Service/Task/TaskHelper.php index 94348880..176bc046 100644 --- a/src/Service/Task/TaskHelper.php +++ b/src/Service/Task/TaskHelper.php @@ -9,19 +9,19 @@ use Tienvx\Bundle\MbtBundle\Model\TaskInterface; use Tienvx\Bundle\MbtBundle\Repository\TaskRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\TaskStepsRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\ExploreStepsRunner; class TaskHelper implements TaskHelperInterface { protected GeneratorManagerInterface $generatorManager; protected TaskRepositoryInterface $taskRepository; - protected TaskStepsRunner $stepsRunner; + protected ExploreStepsRunner $stepsRunner; protected ConfigInterface $config; public function __construct( GeneratorManagerInterface $generatorManager, TaskRepositoryInterface $taskRepository, - TaskStepsRunner $stepsRunner, + ExploreStepsRunner $stepsRunner, ConfigInterface $config ) { $this->generatorManager = $generatorManager; diff --git a/tests/Reducer/HandlerTestCase.php b/tests/Reducer/HandlerTestCase.php index 2a7a21a5..4e281abb 100644 --- a/tests/Reducer/HandlerTestCase.php +++ b/tests/Reducer/HandlerTestCase.php @@ -8,6 +8,8 @@ use Symfony\Component\Messenger\MessageBusInterface; use Throwable; use Tienvx\Bundle\MbtBundle\Entity\Bug; +use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; +use Tienvx\Bundle\MbtBundle\Entity\Task; use Tienvx\Bundle\MbtBundle\Exception\RuntimeException; use Tienvx\Bundle\MbtBundle\Message\ReduceBugMessage; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; @@ -15,7 +17,7 @@ use Tienvx\Bundle\MbtBundle\Reducer\HandlerInterface; use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Builder\StepsBuilderInterface; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\ReduceStepsRunner; use Tienvx\Bundle\MbtBundle\Service\Step\StepHelperInterface; abstract class HandlerTestCase extends TestCase @@ -23,17 +25,18 @@ abstract class HandlerTestCase extends TestCase protected HandlerInterface $handler; protected BugRepositoryInterface $bugRepository; protected MessageBusInterface $messageBus; - protected BugStepsRunner $stepsRunner; + protected ReduceStepsRunner $stepsRunner; protected StepsBuilderInterface $stepsBuilder; protected StepHelperInterface $stepHelper; protected array $newSteps; + protected Revision $revision; protected BugInterface $bug; protected function setUp(): void { $this->bugRepository = $this->createMock(BugRepositoryInterface::class); $this->messageBus = $this->createMock(MessageBusInterface::class); - $this->stepsRunner = $this->createMock(BugStepsRunner::class); + $this->stepsRunner = $this->createMock(ReduceStepsRunner::class); $this->stepsBuilder = $this->createMock(StepsBuilderInterface::class); $this->stepHelper = $this->createMock(StepHelperInterface::class); $this->newSteps = [ @@ -53,6 +56,10 @@ protected function setUp(): void $this->createMock(StepInterface::class), ]); $this->bug->setDebug(true); + $this->revision = new Revision(); + $task = new Task(); + $task->setModelRevision($this->revision); + $this->bug->setTask($task); $this->stepsBuilder ->expects($this->once()) ->method('create') @@ -78,8 +85,8 @@ public function testHandle(?Throwable $exception, bool $updateSteps): void { $this->stepHelper ->expects($this->once()) - ->method('cloneStepsAndResetColor') - ->with($this->newSteps) + ->method('cloneAndResetSteps') + ->with($this->newSteps, $this->revision) ->willReturnArgument(0); $this->stepsRunner->expects($this->once()) ->method('run') diff --git a/tests/Service/Bug/BugHelperTest.php b/tests/Service/Bug/BugHelperTest.php index 5efd72b3..d119ed2c 100644 --- a/tests/Service/Bug/BugHelperTest.php +++ b/tests/Service/Bug/BugHelperTest.php @@ -9,7 +9,9 @@ use Symfony\Component\Messenger\MessageBusInterface; use Throwable; use Tienvx\Bundle\MbtBundle\Entity\Bug; +use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; use Tienvx\Bundle\MbtBundle\Entity\Progress; +use Tienvx\Bundle\MbtBundle\Entity\Task; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; use Tienvx\Bundle\MbtBundle\Message\RecordVideoMessage; use Tienvx\Bundle\MbtBundle\Message\ReportBugMessage; @@ -23,7 +25,7 @@ use Tienvx\Bundle\MbtBundle\Service\Bug\BugHelperInterface; use Tienvx\Bundle\MbtBundle\Service\Bug\BugNotifierInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\RecordStepsRunner; use Tienvx\Bundle\MbtBundle\Service\Step\StepHelperInterface; /** @@ -47,9 +49,10 @@ class BugHelperTest extends TestCase protected MessageBusInterface $messageBus; protected BugNotifierInterface $bugNotifier; protected StepHelperInterface $stepHelper; - protected BugStepsRunner $stepsRunner; + protected RecordStepsRunner $stepsRunner; protected ConfigInterface $config; protected BugHelperInterface $helper; + protected Revision $revision; protected BugInterface $bug; protected ProgressInterface $progress; @@ -60,7 +63,7 @@ protected function setUp(): void $this->messageBus = $this->createMock(MessageBusInterface::class); $this->bugNotifier = $this->createMock(BugNotifierInterface::class); $this->stepHelper = $this->createMock(StepHelperInterface::class); - $this->stepsRunner = $this->createMock(BugStepsRunner::class); + $this->stepsRunner = $this->createMock(RecordStepsRunner::class); $this->config = $this->createMock(ConfigInterface::class); $this->helper = new BugHelper( $this->reducerManager, @@ -84,6 +87,10 @@ protected function setUp(): void $this->createMock(StepInterface::class), ]); $this->bug->setDebug(false); + $this->revision = new Revision(); + $task = new Task(); + $task->setModelRevision($this->revision); + $this->bug->setTask($task); } public function testReduceMissingBug(): void @@ -260,8 +267,8 @@ public function testRecordVideo(?Throwable $exception, ?string $expectedVideoErr $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn($this->bug); $this->stepHelper ->expects($this->once()) - ->method('cloneStepsAndResetColor') - ->with($this->bug->getSteps()) + ->method('cloneAndResetSteps') + ->with($this->bug->getSteps(), $this->revision) ->willReturnArgument(0); $this->bugRepository->expects($this->once())->method('startRecording')->with($this->bug); $this->bugRepository->expects($this->once())->method('stopRecording')->with($this->bug); diff --git a/tests/Service/Step/Runner/BugStepsRunnerTestCase.php b/tests/Service/Step/Runner/BugStepsRunnerTestCase.php new file mode 100644 index 00000000..cc1b4774 --- /dev/null +++ b/tests/Service/Step/Runner/BugStepsRunnerTestCase.php @@ -0,0 +1,16 @@ +handleException + ->expects($this->once()) + ->method('__invoke') + ->with($exception); + } +} diff --git a/tests/Service/Step/Runner/TaskStepsRunnerTest.php b/tests/Service/Step/Runner/ExploreStepsRunnerTest.php similarity index 89% rename from tests/Service/Step/Runner/TaskStepsRunnerTest.php rename to tests/Service/Step/Runner/ExploreStepsRunnerTest.php index eed44269..a066d4a9 100644 --- a/tests/Service/Step/Runner/TaskStepsRunnerTest.php +++ b/tests/Service/Step/Runner/ExploreStepsRunnerTest.php @@ -7,10 +7,10 @@ use Tienvx\Bundle\MbtBundle\Model\BugInterface; use Tienvx\Bundle\MbtBundle\Model\DebugInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\TaskStepsRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\ExploreStepsRunner; /** - * @covers \Tienvx\Bundle\MbtBundle\Service\Step\Runner\TaskStepsRunner + * @covers \Tienvx\Bundle\MbtBundle\Service\Step\Runner\ExploreStepsRunner * @covers \Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepsRunner * * @uses \Tienvx\Bundle\MbtBundle\Entity\Task @@ -21,7 +21,7 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Bug * @uses \Tienvx\Bundle\MbtBundle\Model\Bug\Step */ -class TaskStepsRunnerTest extends StepsRunnerTestCase +class ExploreStepsRunnerTest extends StepsRunnerTestCase { protected ConfigInterface $config; @@ -29,7 +29,7 @@ protected function setUp(): void { parent::setUp(); $this->config = $this->createMock(ConfigInterface::class); - $this->stepsRunner = new TaskStepsRunner($this->selenoidHelper, $this->stepRunner, $this->config); + $this->stepsRunner = new ExploreStepsRunner($this->selenoidHelper, $this->stepRunner, $this->config); } /** diff --git a/tests/Service/Step/Runner/BugStepsRunnerTest.php b/tests/Service/Step/Runner/RecordStepsRunnerTest.php similarity index 57% rename from tests/Service/Step/Runner/BugStepsRunnerTest.php rename to tests/Service/Step/Runner/RecordStepsRunnerTest.php index ee67a662..cfbcd0e5 100644 --- a/tests/Service/Step/Runner/BugStepsRunnerTest.php +++ b/tests/Service/Step/Runner/RecordStepsRunnerTest.php @@ -2,10 +2,10 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service\Step\Runner; -use Exception; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\RecordStepsRunner; /** + * @covers \Tienvx\Bundle\MbtBundle\Service\Step\Runner\RecordStepsRunner * @covers \Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner * @covers \Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepsRunner * @@ -17,19 +17,11 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Bug * @uses \Tienvx\Bundle\MbtBundle\Model\Bug\Step */ -class BugStepsRunnerTest extends StepsRunnerTestCase +class RecordStepsRunnerTest extends BugStepsRunnerTestCase { protected function setUp(): void { parent::setUp(); - $this->stepsRunner = new BugStepsRunner($this->selenoidHelper, $this->stepRunner); - } - - protected function assertHandlingException(Exception $exception, array $bugSteps = []): void - { - $this->handleException - ->expects($this->once()) - ->method('__invoke') - ->with($exception); + $this->stepsRunner = new RecordStepsRunner($this->selenoidHelper, $this->stepRunner); } } diff --git a/tests/Service/Step/Runner/ReduceStepsRunnerTest.php b/tests/Service/Step/Runner/ReduceStepsRunnerTest.php new file mode 100644 index 00000000..274fdeb2 --- /dev/null +++ b/tests/Service/Step/Runner/ReduceStepsRunnerTest.php @@ -0,0 +1,120 @@ +petrinetHelper = $this->createMock(PetrinetHelperInterface::class); + $this->markingHelper = $this->createMock(MarkingHelperInterface::class); + $this->transitionService = $this->createMock(GuardedTransitionServiceInterface::class); + $this->stepsRunner = new ReduceStepsRunner( + $this->selenoidHelper, + $this->stepRunner, + $this->petrinetHelper, + $this->markingHelper, + $this->transitionService + ); + $this->petrinet = $this->createMock(PetrinetInterface::class); + $this->petrinetHelper + ->expects($this->once()) + ->method('build') + ->with($this->revision) + ->willReturn($this->petrinet); + $this->marking = $this->createMock(ColorfulMarkingInterface::class); + $this->transitions = [ + 0 => $this->createMock(TransitionInterface::class), + 1 => $this->createMock(TransitionInterface::class), + 2 => $this->createMock(TransitionInterface::class), + 3 => $this->createMock(TransitionInterface::class), + ]; + } + + /** + * @dataProvider entityProvider + */ + public function testCanNotRunThirdStep(DebugInterface $entity): void + { + $this->selenoidHelper + ->expects($this->once()) + ->method('createDriver') + ->with($entity) + ->willReturn($this->driver); + $this->driver->expects($this->once())->method('quit'); + $this->assertRunSteps(array_slice($this->steps, 0, 2), null, true); + $this->handleException->expects($this->never())->method('__invoke'); + $this->stepsRunner->run($this->steps, $entity, $this->handleException); + } + + protected function assertRunSteps( + array $steps = [], + ?Exception $exception = null, + bool $nextStepDisabled = false + ): void { + parent::assertRunSteps($steps, $exception); + $this->markingHelper + ->expects($this->exactly(count($steps) + $nextStepDisabled)) + ->method('getMarking') + ->withConsecutive(...array_map( + fn (StepInterface $step) => [$this->petrinet, $step->getPlaces(), $step->getColor()], + array_slice($this->steps, 0, count($steps) + $nextStepDisabled) + )) + ->willReturn($this->marking); + $this->petrinet + ->expects($this->exactly(count($steps) + $nextStepDisabled)) + ->method('getTransitions') + ->willReturn($this->transitions); + if (count($steps) < count($this->steps)) { + $this->transitionService + ->expects($this->exactly(count($steps) + $nextStepDisabled)) + ->method('isEnabled') + ->withConsecutive(...array_map( + fn (StepInterface $step) => [$this->transitions[$step->getTransition()], $this->marking], + array_slice($this->steps, 0, count($steps) + $nextStepDisabled) + )) + ->willReturnOnConsecutiveCalls(...[...array_fill(0, count($steps), true), !$nextStepDisabled]); + } else { + $this->transitionService + ->expects($this->exactly(count($this->steps))) + ->method('isEnabled') + ->withConsecutive(...array_map( + fn (StepInterface $step) => [$this->transitions[$step->getTransition()], $this->marking], + $this->steps + )) + ->willReturn(true); + } + } +} diff --git a/tests/Service/Step/StepHelperTest.php b/tests/Service/Step/StepHelperTest.php index 41b4abe6..83f7353c 100644 --- a/tests/Service/Step/StepHelperTest.php +++ b/tests/Service/Step/StepHelperTest.php @@ -6,6 +6,8 @@ use SingleColorPetrinet\Model\Color; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; +use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; +use Tienvx\Bundle\MbtBundle\Service\Model\ModelHelperInterface; use Tienvx\Bundle\MbtBundle\Service\Step\StepHelper; use Tienvx\Bundle\MbtBundle\ValueObject\Bug\Step; @@ -16,11 +18,21 @@ */ class StepHelperTest extends TestCase { + protected ModelHelperInterface $modelHelper; protected StepHelper $stepHelper; + protected RevisionInterface $revision; + protected array $startPlaces = [1, 2, 3]; protected function setUp(): void { - $this->stepHelper = new StepHelper(); + $this->modelHelper = $this->createMock(ModelHelperInterface::class); + $this->stepHelper = new StepHelper($this->modelHelper); + $this->revision = $this->createMock(RevisionInterface::class); + $this->modelHelper + ->expects($this->once()) + ->method('getStartPlaceIds') + ->with($this->revision) + ->willReturn($this->startPlaces); } public function testCloneInvalidSteps(): void @@ -28,7 +40,7 @@ public function testCloneInvalidSteps(): void $this->expectExceptionObject( new UnexpectedValueException(sprintf('Step must be instance of "%s".', StepInterface::class)) ); - $this->stepHelper->cloneStepsAndResetColor([new \stdClass()]); + $this->stepHelper->cloneAndResetSteps([new \stdClass()], $this->revision); } public function testCloneStepsAndResetColor(): void @@ -39,18 +51,19 @@ public function testCloneStepsAndResetColor(): void new Step([2], new Color(), 2), new Step([3], new Color(), 3), ]; - $newSteps = $this->stepHelper->cloneStepsAndResetColor($steps); + $newSteps = $this->stepHelper->cloneAndResetSteps($steps, $this->revision); $this->assertCount(count($steps), $newSteps); foreach ($newSteps as $index => $newStep) { $this->assertInstanceOf(StepInterface::class, $newStep); - $this->assertSame($steps[$index]->getPlaces(), $newStep->getPlaces()); $this->assertSame($steps[$index]->getTransition(), $newStep->getTransition()); $this->assertNotSame($steps[$index]->getColor(), $newStep->getColor()); if ($index > 0) { $this->assertNotSame($steps[$index - 1]->getColor(), $newStep->getColor()); $this->assertSame($steps[$index - 1]->getColor()->getValues(), $newStep->getColor()->getValues()); + $this->assertSame($steps[$index - 1]->getPlaces(), $newStep->getPlaces()); } else { $this->assertSame([], $newStep->getColor()->getValues()); + $this->assertSame($this->startPlaces, $newStep->getPlaces()); } } } diff --git a/tests/Service/Task/TaskHelperTest.php b/tests/Service/Task/TaskHelperTest.php index c0527f56..beac0f02 100644 --- a/tests/Service/Task/TaskHelperTest.php +++ b/tests/Service/Task/TaskHelperTest.php @@ -14,7 +14,7 @@ use Tienvx\Bundle\MbtBundle\Model\TaskInterface; use Tienvx\Bundle\MbtBundle\Repository\TaskRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\TaskStepsRunner; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\ExploreStepsRunner; use Tienvx\Bundle\MbtBundle\Service\Task\TaskHelper; use Tienvx\Bundle\MbtBundle\Service\Task\TaskHelperInterface; use Tienvx\Bundle\MbtBundle\ValueObject\Bug\Step; @@ -35,7 +35,7 @@ class TaskHelperTest extends TestCase protected array $steps; protected GeneratorManagerInterface $generatorManager; protected TaskRepositoryInterface $taskRepository; - protected TaskStepsRunner $stepsRunner; + protected ExploreStepsRunner $stepsRunner; protected TaskHelperInterface $taskHelper; protected ConfigInterface $config; protected TaskInterface $task; @@ -50,7 +50,7 @@ protected function setUp(): void ]; $this->generatorManager = $this->createMock(GeneratorManagerInterface::class); $this->taskRepository = $this->createMock(TaskRepositoryInterface::class); - $this->stepsRunner = $this->createMock(TaskStepsRunner::class); + $this->stepsRunner = $this->createMock(ExploreStepsRunner::class); $this->config = $this->createMock(ConfigInterface::class); $this->taskHelper = new TaskHelper( $this->generatorManager, From 5651459d9fcf5b8316a2ecf23afdcf308aeaa4cd Mon Sep 17 00:00:00 2001 From: tienvx Date: Wed, 27 Apr 2022 22:14:21 +0700 Subject: [PATCH 44/85] Isolate transition expression from commands values --- src/Command/CommandPreprocessor.php | 16 ++-- src/Command/CommandPreprocessorInterface.php | 4 +- src/Command/CommandRunnerInterface.php | 4 +- src/Command/CommandRunnerManager.php | 6 +- src/Command/CommandRunnerManagerInterface.php | 4 +- src/Command/Runner/AlertCommandRunner.php | 4 +- src/Command/Runner/AssertionRunner.php | 6 +- src/Command/Runner/KeyboardCommandRunner.php | 4 +- src/Command/Runner/MouseCommandRunner.php | 4 +- src/Command/Runner/ScriptCommandRunner.php | 8 +- src/Command/Runner/StoreCommandRunner.php | 20 ++--- src/Command/Runner/WaitCommandRunner.php | 4 +- src/Command/Runner/WindowCommandRunner.php | 4 +- src/Model/Model/Revision/Transition.php | 13 +++ .../Model/Revision/TransitionInterface.php | 4 + src/Model/Values.php | 32 +++++++ src/Model/ValuesInterface.php | 12 +++ src/Service/Petrinet/PetrinetHelper.php | 8 +- .../Step/Builder/ShortestPathStepsBuilder.php | 14 +--- src/Service/Step/Runner/StepRunner.php | 11 +-- src/ValueObject/Model/Transition.php | 8 ++ tests/Command/CommandPreprocessorTest.php | 7 +- tests/Command/CommandRunnerManagerTest.php | 83 ++++++++++++------- .../Command/Runner/AlertCommandRunnerTest.php | 6 +- tests/Command/Runner/AssertionRunnerTest.php | 68 +++++++-------- .../Runner/KeyboardCommandRunnerTest.php | 4 +- .../Command/Runner/MouseCommandRunnerTest.php | 50 +++++------ tests/Command/Runner/RunnerTestCase.php | 6 +- .../Runner/ScriptCommandRunnerTest.php | 16 ++-- .../Command/Runner/StoreCommandRunnerTest.php | 32 +++---- .../Command/Runner/WaitCommandRunnerTest.php | 14 ++-- .../Runner/WindowCommandRunnerTest.php | 16 ++-- tests/Model/Model/Revision/TransitionTest.php | 6 +- tests/Model/Model/RevisionTest.php | 3 + tests/Model/ValuesTest.php | 39 +++++++++ tests/Service/Petrinet/PetrinetHelperTest.php | 8 ++ tests/Service/Step/Runner/StepRunnerTest.php | 21 +++-- 37 files changed, 362 insertions(+), 207 deletions(-) create mode 100644 src/Model/Values.php create mode 100644 src/Model/ValuesInterface.php create mode 100644 tests/Model/ValuesTest.php diff --git a/src/Command/CommandPreprocessor.php b/src/Command/CommandPreprocessor.php index 6a657468..5ad0c5e9 100644 --- a/src/Command/CommandPreprocessor.php +++ b/src/Command/CommandPreprocessor.php @@ -2,24 +2,24 @@ namespace Tienvx\Bundle\MbtBundle\Command; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\Command; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; class CommandPreprocessor implements CommandPreprocessorInterface { - public function process(CommandInterface $command, ColorInterface $color): CommandInterface + public function process(CommandInterface $command, ValuesInterface $values): CommandInterface { $processed = new Command(); $processed->setCommand($command->getCommand()); $processed->setTarget( $command->getTarget() - ? $this->replaceVariables($command->getTarget(), $color->getValues()) + ? $this->replaceVariables($command->getTarget(), $values->getValues()) : $command->getTarget() ); $processed->setValue( $command->getValue() - ? $this->replaceVariables($command->getValue(), $color->getValues()) + ? $this->replaceVariables($command->getValue(), $values->getValues()) : $command->getValue() ); @@ -28,8 +28,10 @@ public function process(CommandInterface $command, ColorInterface $color): Comma protected function replaceVariables(string $text, array $values): string { - return preg_replace_callback('/\$\{(.*?)\}/', function ($matches) use ($values) { - return $values[$matches[1]] ?? $matches[1]; - }, $text); + return preg_replace_callback( + '/\$\{(.*?)\}/', + fn (array $matches): string => $values[$matches[1]] ?? $matches[1], + $text + ); } } diff --git a/src/Command/CommandPreprocessorInterface.php b/src/Command/CommandPreprocessorInterface.php index 54a367d5..8e72880e 100644 --- a/src/Command/CommandPreprocessorInterface.php +++ b/src/Command/CommandPreprocessorInterface.php @@ -2,10 +2,10 @@ namespace Tienvx\Bundle\MbtBundle\Command; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; interface CommandPreprocessorInterface { - public function process(CommandInterface $command, ColorInterface $color): CommandInterface; + public function process(CommandInterface $command, ValuesInterface $values): CommandInterface; } diff --git a/src/Command/CommandRunnerInterface.php b/src/Command/CommandRunnerInterface.php index bb22e055..fc943033 100644 --- a/src/Command/CommandRunnerInterface.php +++ b/src/Command/CommandRunnerInterface.php @@ -3,8 +3,8 @@ namespace Tienvx\Bundle\MbtBundle\Command; use Facebook\WebDriver\Remote\RemoteWebDriver; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; interface CommandRunnerInterface { @@ -35,5 +35,5 @@ public function validateTarget(CommandInterface $command): bool; public function supports(CommandInterface $command): bool; - public function run(CommandInterface $command, ColorInterface $color, RemoteWebDriver $driver): void; + public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void; } diff --git a/src/Command/CommandRunnerManager.php b/src/Command/CommandRunnerManager.php index c5f25e59..013216a4 100644 --- a/src/Command/CommandRunnerManager.php +++ b/src/Command/CommandRunnerManager.php @@ -3,8 +3,8 @@ namespace Tienvx\Bundle\MbtBundle\Command; use Facebook\WebDriver\Remote\RemoteWebDriver; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; class CommandRunnerManager implements CommandRunnerManagerInterface { @@ -43,11 +43,11 @@ public function validateTarget(CommandInterface $command): bool return false; } - public function run(CommandInterface $command, ColorInterface $color, RemoteWebDriver $driver): void + public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void { foreach ($this->runners as $runner) { if ($runner instanceof CommandRunnerInterface && $runner->supports($command)) { - $runner->run($this->commandPreprocessor->process($command, $color), $color, $driver); + $runner->run($this->commandPreprocessor->process($command, $values), $values, $driver); break; } } diff --git a/src/Command/CommandRunnerManagerInterface.php b/src/Command/CommandRunnerManagerInterface.php index f4db5a6e..d68e3d88 100644 --- a/src/Command/CommandRunnerManagerInterface.php +++ b/src/Command/CommandRunnerManagerInterface.php @@ -3,8 +3,8 @@ namespace Tienvx\Bundle\MbtBundle\Command; use Facebook\WebDriver\Remote\RemoteWebDriver; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; interface CommandRunnerManagerInterface { @@ -16,5 +16,5 @@ public function getCommandsRequireValue(): array; public function validateTarget(CommandInterface $command): bool; - public function run(CommandInterface $command, ColorInterface $color, RemoteWebDriver $driver): void; + public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void; } diff --git a/src/Command/Runner/AlertCommandRunner.php b/src/Command/Runner/AlertCommandRunner.php index 309a2136..a09787e6 100644 --- a/src/Command/Runner/AlertCommandRunner.php +++ b/src/Command/Runner/AlertCommandRunner.php @@ -3,9 +3,9 @@ namespace Tienvx\Bundle\MbtBundle\Command\Runner; use Facebook\WebDriver\Remote\RemoteWebDriver; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Command\CommandRunner; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; class AlertCommandRunner extends CommandRunner { @@ -38,7 +38,7 @@ public function getCommandsRequireValue(): array return []; } - public function run(CommandInterface $command, ColorInterface $color, RemoteWebDriver $driver): void + public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void { switch ($command->getCommand()) { case self::ACCEPT_ALERT: diff --git a/src/Command/Runner/AssertionRunner.php b/src/Command/Runner/AssertionRunner.php index 7ca303b1..21466672 100644 --- a/src/Command/Runner/AssertionRunner.php +++ b/src/Command/Runner/AssertionRunner.php @@ -4,9 +4,9 @@ use Exception; use Facebook\WebDriver\Remote\RemoteWebDriver; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Command\CommandRunner; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; class AssertionRunner extends CommandRunner { @@ -72,11 +72,11 @@ public function getCommandsRequireValue(): array ]; } - public function run(CommandInterface $command, ColorInterface $color, RemoteWebDriver $driver): void + public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void { switch ($command->getCommand()) { case self::ASSERT: - $actual = $color->getValue($command->getTarget()); + $actual = $values->getValue($command->getTarget()); $this->assert( $actual === $command->getValue(), sprintf('Actual value "%s" did not match "%s"', $actual, $command->getValue()) diff --git a/src/Command/Runner/KeyboardCommandRunner.php b/src/Command/Runner/KeyboardCommandRunner.php index 2c984f7d..21ef7a38 100644 --- a/src/Command/Runner/KeyboardCommandRunner.php +++ b/src/Command/Runner/KeyboardCommandRunner.php @@ -3,9 +3,9 @@ namespace Tienvx\Bundle\MbtBundle\Command\Runner; use Facebook\WebDriver\Remote\RemoteWebDriver; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Command\CommandRunner; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; class KeyboardCommandRunner extends CommandRunner { @@ -30,7 +30,7 @@ public function getCommandsRequireValue(): array return []; } - public function run(CommandInterface $command, ColorInterface $color, RemoteWebDriver $driver): void + public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void { switch ($command->getCommand()) { case self::TYPE: diff --git a/src/Command/Runner/MouseCommandRunner.php b/src/Command/Runner/MouseCommandRunner.php index b379b765..8a2e5e60 100644 --- a/src/Command/Runner/MouseCommandRunner.php +++ b/src/Command/Runner/MouseCommandRunner.php @@ -5,9 +5,9 @@ use Exception; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\WebDriverPoint; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Command\CommandRunner; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; class MouseCommandRunner extends CommandRunner { @@ -72,7 +72,7 @@ public function getCommandsRequireValue(): array ]; } - public function run(CommandInterface $command, ColorInterface $color, RemoteWebDriver $driver): void + public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void { switch ($command->getCommand()) { case self::ADD_SELECTION: diff --git a/src/Command/Runner/ScriptCommandRunner.php b/src/Command/Runner/ScriptCommandRunner.php index db1970db..7dda669a 100644 --- a/src/Command/Runner/ScriptCommandRunner.php +++ b/src/Command/Runner/ScriptCommandRunner.php @@ -3,9 +3,9 @@ namespace Tienvx\Bundle\MbtBundle\Command\Runner; use Facebook\WebDriver\Remote\RemoteWebDriver; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Command\CommandRunner; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; class ScriptCommandRunner extends CommandRunner { @@ -35,7 +35,7 @@ public function getCommandsRequireValue(): array ]; } - public function run(CommandInterface $command, ColorInterface $color, RemoteWebDriver $driver): void + public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void { switch ($command->getCommand()) { case self::RUN_SCRIPT: @@ -44,13 +44,13 @@ public function run(CommandInterface $command, ColorInterface $color, RemoteWebD case self::EXECUTE_SCRIPT: $value = $driver->executeScript($command->getTarget()); if ($command->getValue()) { - $color->setValue($command->getValue(), $value); + $values->setValue($command->getValue(), $value); } break; case self::EXECUTE_ASYNC_SCRIPT: $value = $driver->executeAsyncScript($command->getTarget()); if ($command->getValue()) { - $color->setValue($command->getValue(), $value); + $values->setValue($command->getValue(), $value); } break; default: diff --git a/src/Command/Runner/StoreCommandRunner.php b/src/Command/Runner/StoreCommandRunner.php index 78ea80e5..6aaa07ca 100644 --- a/src/Command/Runner/StoreCommandRunner.php +++ b/src/Command/Runner/StoreCommandRunner.php @@ -3,9 +3,9 @@ namespace Tienvx\Bundle\MbtBundle\Command\Runner; use Facebook\WebDriver\Remote\RemoteWebDriver; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Command\CommandRunner; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; class StoreCommandRunner extends CommandRunner { @@ -58,48 +58,48 @@ public function getCommandsRequireValue(): array ]; } - public function run(CommandInterface $command, ColorInterface $color, RemoteWebDriver $driver): void + public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void { switch ($command->getCommand()) { case self::STORE: - $color->setValue($command->getValue(), $command->getTarget()); + $values->setValue($command->getValue(), $command->getTarget()); break; case self::STORE_ATTRIBUTE: list($elementLocator, $attributeName) = explode('@', $command->getTarget(), 2); - $color->setValue( + $values->setValue( $command->getValue(), $driver->findElement($this->getSelector($elementLocator))->getAttribute($attributeName) ); break; case self::STORE_ELEMENT_COUNT: - $color->setValue( + $values->setValue( $command->getValue(), count($driver->findElements($this->getSelector($command->getTarget()))) ); break; case self::STORE_JSON: - $color->setValue( + $values->setValue( $command->getValue(), json_decode($command->getTarget()) ); break; case self::STORE_TEXT: - $color->setValue( + $values->setValue( $command->getValue(), $driver->findElement($this->getSelector($command->getTarget()))->getText() ); break; case self::STORE_TITLE: - $color->setValue($command->getTarget(), $driver->getTitle()); + $values->setValue($command->getTarget(), $driver->getTitle()); break; case self::STORE_VALUE: - $color->setValue( + $values->setValue( $command->getValue(), $driver->findElement($this->getSelector($command->getTarget()))->getAttribute('value') ); break; case self::STORE_WINDOW_HANDLE: - $color->setValue($command->getTarget(), $driver->getWindowHandle()); + $values->setValue($command->getTarget(), $driver->getWindowHandle()); break; default: break; diff --git a/src/Command/Runner/WaitCommandRunner.php b/src/Command/Runner/WaitCommandRunner.php index 069c18bc..6873fa3b 100644 --- a/src/Command/Runner/WaitCommandRunner.php +++ b/src/Command/Runner/WaitCommandRunner.php @@ -4,9 +4,9 @@ use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\WebDriverExpectedCondition; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Command\CommandRunner; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; class WaitCommandRunner extends CommandRunner { @@ -39,7 +39,7 @@ public function getCommandsRequireValue(): array return $this->getAllCommands(); } - public function run(CommandInterface $command, ColorInterface $color, RemoteWebDriver $driver): void + public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void { switch ($command->getCommand()) { case self::WAIT_FOR_ELEMENT_EDITABLE: diff --git a/src/Command/Runner/WindowCommandRunner.php b/src/Command/Runner/WindowCommandRunner.php index e5a7d03e..dc83fa55 100644 --- a/src/Command/Runner/WindowCommandRunner.php +++ b/src/Command/Runner/WindowCommandRunner.php @@ -6,9 +6,9 @@ use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\WebDriverDimension; use Facebook\WebDriver\WebDriverExpectedCondition; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Command\CommandRunner; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; class WindowCommandRunner extends CommandRunner { @@ -44,7 +44,7 @@ public function getCommandsRequireValue(): array return []; } - public function run(CommandInterface $command, ColorInterface $color, RemoteWebDriver $driver): void + public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void { switch ($command->getCommand()) { case self::OPEN: diff --git a/src/Model/Model/Revision/Transition.php b/src/Model/Model/Revision/Transition.php index a0afe081..22063240 100644 --- a/src/Model/Model/Revision/Transition.php +++ b/src/Model/Model/Revision/Transition.php @@ -11,6 +11,7 @@ class Transition implements TransitionInterface protected string $label = ''; protected ?string $guard = null; + protected ?string $expression = null; protected array $fromPlaces = []; protected array $toPlaces = []; @@ -23,6 +24,7 @@ public function __unserialize(array $data) { $this->label = $data['label']; $this->guard = $data['guard']; + $this->expression = $data['expression']; $this->fromPlaces = $data['fromPlaces']; $this->toPlaces = $data['toPlaces']; $this->commands = array_map([CommandFactory::class, 'createFromArray'], $data['commands']); @@ -48,6 +50,16 @@ public function setGuard(?string $guard): void $this->guard = $guard; } + public function getExpression(): ?string + { + return $this->expression; + } + + public function setExpression(?string $expression): void + { + $this->expression = $expression; + } + public function getFromPlaces(): array { return $this->fromPlaces; @@ -91,6 +103,7 @@ public function toArray(): array return [ 'label' => $this->label, 'guard' => $this->guard, + 'expression' => $this->expression, 'fromPlaces' => $this->fromPlaces, 'toPlaces' => $this->toPlaces, 'commands' => array_map(fn (CommandInterface $command) => $command->toArray(), $this->commands), diff --git a/src/Model/Model/Revision/TransitionInterface.php b/src/Model/Model/Revision/TransitionInterface.php index 6e1938e4..49559ca7 100644 --- a/src/Model/Model/Revision/TransitionInterface.php +++ b/src/Model/Model/Revision/TransitionInterface.php @@ -12,6 +12,10 @@ public function getGuard(): ?string; public function setGuard(?string $guard): void; + public function getExpression(): ?string; + + public function setExpression(?string $expression): void; + public function getCommands(): array; public function setCommands(array $commands): void; diff --git a/src/Model/Values.php b/src/Model/Values.php new file mode 100644 index 00000000..0d689c59 --- /dev/null +++ b/src/Model/Values.php @@ -0,0 +1,32 @@ +values = []; + + foreach ($values as $key => $value) { + $this->setValue($key, $value); + } + } + + public function getValues(): array + { + return $this->values; + } + + public function setValue(string $key, mixed $value): void + { + $this->values[$key] = $value; + } + + public function getValue(string $key): mixed + { + return $this->values[$key] ?? null; + } +} diff --git a/src/Model/ValuesInterface.php b/src/Model/ValuesInterface.php new file mode 100644 index 00000000..ad05d826 --- /dev/null +++ b/src/Model/ValuesInterface.php @@ -0,0 +1,12 @@ +getValues() ) : null; - $transitions[$index] = $builder->transition($guardCallback); + $expressionCallback = $transition->getExpression() + ? fn (ColorInterface $color): array => (array) $this->expressionLanguage->evaluate( + $transition->getExpression(), + $color->getValues() + ) + : null; + $transitions[$index] = $builder->transition($guardCallback, $expressionCallback); $transitions[$index]->setId($index); } } diff --git a/src/Service/Step/Builder/ShortestPathStepsBuilder.php b/src/Service/Step/Builder/ShortestPathStepsBuilder.php index 6c35355a..738a602c 100644 --- a/src/Service/Step/Builder/ShortestPathStepsBuilder.php +++ b/src/Service/Step/Builder/ShortestPathStepsBuilder.php @@ -29,19 +29,9 @@ public function __construct( */ public function create(BugInterface $bug, int $from, int $to): Generator { - foreach ($bug->getSteps() as $index => $step) { - if ($index < $from) { - yield $step; - } - } - + yield from array_slice($bug->getSteps(), 0, $from); yield from $this->getSteps($bug, $from, $to); - - foreach ($bug->getSteps() as $index => $step) { - if ($index > $to) { - yield $step; - } - } + yield from array_slice($bug->getSteps(), $to + 1); } protected function getSteps(BugInterface $bug, int $from, int $to): iterable diff --git a/src/Service/Step/Runner/StepRunner.php b/src/Service/Step/Runner/StepRunner.php index 14a34186..6b76c602 100644 --- a/src/Service/Step/Runner/StepRunner.php +++ b/src/Service/Step/Runner/StepRunner.php @@ -3,13 +3,13 @@ namespace Tienvx\Bundle\MbtBundle\Service\Step\Runner; use Facebook\WebDriver\Remote\RemoteWebDriver; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Command\CommandRunnerManagerInterface; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\PlaceInterface; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\TransitionInterface; use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; +use Tienvx\Bundle\MbtBundle\Model\Values; class StepRunner implements StepRunnerInterface { @@ -24,21 +24,22 @@ public function run(StepInterface $step, RevisionInterface $revision, RemoteWebD { $transition = $revision->getTransition($step->getTransition()); if ($transition instanceof TransitionInterface) { - $this->executeCommands($transition->getCommands(), $step->getColor(), $driver); + $this->runCommands($transition->getCommands(), $driver); } foreach ($step->getPlaces() as $place => $tokens) { $place = $revision->getPlace($place); if ($place instanceof PlaceInterface) { - $this->executeCommands($place->getCommands(), $step->getColor(), $driver); + $this->runCommands($place->getCommands(), $driver); } } } - protected function executeCommands(array $commands, ColorInterface $color, RemoteWebDriver $driver): void + protected function runCommands(array $commands, RemoteWebDriver $driver): void { + $values = new Values(); foreach ($commands as $command) { if ($command instanceof CommandInterface) { - $this->commandRunnerManager->run($command, $color, $driver); + $this->commandRunnerManager->run($command, $values, $driver); } } } diff --git a/src/ValueObject/Model/Transition.php b/src/ValueObject/Model/Transition.php index 0eb0023e..242b07db 100644 --- a/src/ValueObject/Model/Transition.php +++ b/src/ValueObject/Model/Transition.php @@ -20,6 +20,14 @@ class Transition extends TransitionModel */ protected ?string $guard = null; + /** + * @Assert\AtLeastOneOf({ + * @Assert\IsNull, + * @Assert\ExpressionLanguageSyntax + * }) + */ + protected ?string $expression = null; + /** * @Assert\All({ * @Assert\Type("\Tienvx\Bundle\MbtBundle\ValueObject\Model\Command") diff --git a/tests/Command/CommandPreprocessorTest.php b/tests/Command/CommandPreprocessorTest.php index 41623619..076ac292 100644 --- a/tests/Command/CommandPreprocessorTest.php +++ b/tests/Command/CommandPreprocessorTest.php @@ -3,7 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Command; use PHPUnit\Framework\TestCase; -use SingleColorPetrinet\Model\Color; use Tienvx\Bundle\MbtBundle\Command\CommandPreprocessor; use Tienvx\Bundle\MbtBundle\Command\Runner\KeyboardCommandRunner; use Tienvx\Bundle\MbtBundle\Command\Runner\MouseCommandRunner; @@ -11,11 +10,13 @@ use Tienvx\Bundle\MbtBundle\Command\Runner\StoreCommandRunner; use Tienvx\Bundle\MbtBundle\Command\Runner\WindowCommandRunner; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\Command; +use Tienvx\Bundle\MbtBundle\Model\Values; /** * @covers \Tienvx\Bundle\MbtBundle\Command\CommandPreprocessor * * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision\Command + * @uses \Tienvx\Bundle\MbtBundle\Model\Values */ class CommandPreprocessorTest extends TestCase { @@ -34,10 +35,8 @@ public function testProcess( $command->setCommand($commandString); $command->setTarget($target); $command->setValue($value); - $color = new Color(); - $color->setValues($values); $preprocessor = new CommandPreprocessor(); - $newCommand = $preprocessor->process($command, $color); + $newCommand = $preprocessor->process($command, new Values($values)); $this->assertSame($commandString, $newCommand->getCommand()); $this->assertSame($newTarget, $newCommand->getTarget()); $this->assertSame($newValue, $newCommand->getValue()); diff --git a/tests/Command/CommandRunnerManagerTest.php b/tests/Command/CommandRunnerManagerTest.php index 5dcb20c0..4f1fabfd 100644 --- a/tests/Command/CommandRunnerManagerTest.php +++ b/tests/Command/CommandRunnerManagerTest.php @@ -5,11 +5,11 @@ use Facebook\WebDriver\Remote\RemoteWebDriver; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Command\CommandPreprocessorInterface; -use Tienvx\Bundle\MbtBundle\Command\CommandRunner; +use Tienvx\Bundle\MbtBundle\Command\CommandRunnerInterface; use Tienvx\Bundle\MbtBundle\Command\CommandRunnerManager; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; /** * @covers \Tienvx\Bundle\MbtBundle\Command\CommandRunnerManager @@ -22,15 +22,24 @@ class CommandRunnerManagerTest extends TestCase protected array $runners; protected CommandPreprocessorInterface $preprocessor; protected CommandRunnerManager $manager; + protected CommandInterface $command; + protected CommandInterface $processedCommand; + protected RemoteWebDriver $driver; + protected ValuesInterface $values; protected function setUp(): void { $this->runners = [ - $runner1 = $this->createMock(CommandRunner::class), - $runner2 = $this->createMock(CommandRunner::class), + $runner1 = $this->createMock(CommandRunnerInterface::class), + $runner2 = $this->createMock(CommandRunnerInterface::class), + $runner3 = $this->createMock(CommandRunnerInterface::class), ]; $this->preprocessor = $this->createMock(CommandPreprocessorInterface::class); $this->manager = new CommandRunnerManager($this->runners, $this->preprocessor); + $this->command = $this->createMock(CommandInterface::class); + $this->processedCommand = $this->createMock(CommandInterface::class); + $this->driver = $this->createMock(RemoteWebDriver::class); + $this->values = $this->createMock(ValuesInterface::class); } public function testGetAllCommands(): void @@ -42,10 +51,16 @@ public function testGetAllCommands(): void $this->runners[1]->expects($this->once())->method('getAllCommands')->willReturn([ 'Action 3' => 'action3', ]); + $this->runners[2]->expects($this->once())->method('getAllCommands')->willReturn([ + 'Action 4' => 'action4', + 'Action 5' => 'action5', + ]); $this->assertSame([ 'Action 1' => 'action1', 'Action 2' => 'action2', 'Action 3' => 'action3', + 'Action 4' => 'action4', + 'Action 5' => 'action5', ], $this->manager->getAllCommands()); } @@ -58,10 +73,14 @@ public function testGetCommandsRequireTarget(): void 'Action 2' => 'action2', 'Action 3' => 'action3', ]); + $this->runners[2]->expects($this->once())->method('getCommandsRequireTarget')->willReturn([ + 'Action 4' => 'action4', + ]); $this->assertSame([ 'Action 1' => 'action1', 'Action 2' => 'action2', 'Action 3' => 'action3', + 'Action 4' => 'action4', ], $this->manager->getCommandsRequireTarget()); } @@ -75,39 +94,45 @@ public function testGetCommandsRequireValue(): void 'Action 2' => 'action2', 'Action 3' => 'action3', ]); + $this->runners[2]->expects($this->once())->method('getCommandsRequireValue')->willReturn([ + 'Action 5' => 'action5', + ]); $this->assertSame([ 'Action 1' => 'action1', 'Action 4' => 'action4', 'Action 2' => 'action2', 'Action 3' => 'action3', + 'Action 5' => 'action5', ], $this->manager->getCommandsRequireValue()); } - public function testRunCommandInSecondRunner(): void - { - $command = $this->createMock(CommandInterface::class); - $newCommand = $this->createMock(CommandInterface::class); - $driver = $this->createMock(RemoteWebDriver::class); - $color = $this->createMock(ColorInterface::class); - $this->runners[0]->expects($this->once())->method('supports')->willReturn(false); - $this->runners[0]->expects($this->never())->method('run'); - $this->runners[1]->expects($this->once())->method('supports')->willReturn(true); - $this->runners[1]->expects($this->once())->method('run')->with($newCommand, $color, $driver); - $this->preprocessor->expects($this->once())->method('process')->with($command, $color)->willReturn($newCommand); - $this->manager->run($command, $color, $driver); - } - - public function testRunCommandInFirstRunner(): void + /** + * @testWith [0] + * [1] + * [2] + */ + public function testRunCommand(int $support): void { - $command = $this->createMock(CommandInterface::class); - $newCommand = $this->createMock(CommandInterface::class); - $driver = $this->createMock(RemoteWebDriver::class); - $color = $this->createMock(ColorInterface::class); - $this->runners[0]->expects($this->once())->method('supports')->willReturn(true); - $this->runners[0]->expects($this->once())->method('run')->with($newCommand, $color, $driver); - $this->runners[1]->expects($this->never())->method('supports'); - $this->runners[1]->expects($this->never())->method('run'); - $this->preprocessor->expects($this->once())->method('process')->with($command, $color)->willReturn($newCommand); - $this->manager->run($command, $color, $driver); + foreach ($this->runners as $index => $runner) { + if ($index < $support) { + $runner->expects($this->once())->method('supports')->willReturn(false); + $runner->expects($this->never())->method('run'); + } elseif ($index === $support) { + $runner->expects($this->once())->method('supports')->willReturn(true); + $runner + ->expects($this->once()) + ->method('run') + ->with($this->processedCommand, $this->values, $this->driver); + } else { + $runner->expects($this->never())->method('supports'); + $runner->expects($this->never())->method('run'); + } + } + $this->preprocessor + ->expects($this->once()) + ->method('process') + ->with($this->command, $this->values) + ->willReturn($this->processedCommand); + $this->manager->run($this->command, $this->values, $this->driver); } } diff --git a/tests/Command/Runner/AlertCommandRunnerTest.php b/tests/Command/Runner/AlertCommandRunnerTest.php index 2fe12f93..78764c98 100644 --- a/tests/Command/Runner/AlertCommandRunnerTest.php +++ b/tests/Command/Runner/AlertCommandRunnerTest.php @@ -33,7 +33,7 @@ public function testAcceptAlert(string $acceptAlertCommand): void $targetLocator = $this->createMock(RemoteTargetLocator::class); $targetLocator->expects($this->once())->method('alert')->willReturn($alert); $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } /** @@ -48,7 +48,7 @@ public function testDismissAlert(string $acceptAlertCommand): void $targetLocator = $this->createMock(RemoteTargetLocator::class); $targetLocator->expects($this->once())->method('alert')->willReturn($alert); $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAnswerPrompt(): void @@ -62,7 +62,7 @@ public function testAnswerPrompt(): void $targetLocator = $this->createMock(RemoteTargetLocator::class); $targetLocator->expects($this->once())->method('alert')->willReturn($alert); $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function acceptAlertCommandProvider(): array diff --git a/tests/Command/Runner/AssertionRunnerTest.php b/tests/Command/Runner/AssertionRunnerTest.php index 188d514f..b2f4c522 100644 --- a/tests/Command/Runner/AssertionRunnerTest.php +++ b/tests/Command/Runner/AssertionRunnerTest.php @@ -30,8 +30,8 @@ public function testAssertPassed(): void $command->setCommand(AssertionRunner::ASSERT); $command->setTarget('var name'); $command->setValue('var value'); - $this->color->expects($this->once())->method('getValue')->with('var name')->willReturn('var value'); - $this->runner->run($command, $this->color, $this->driver); + $this->values->expects($this->once())->method('getValue')->with('var name')->willReturn('var value'); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertFailed(): void @@ -44,8 +44,8 @@ public function testAssertFailed(): void $command->setCommand(AssertionRunner::ASSERT); $command->setTarget('var name'); $command->setValue($expected); - $this->color->expects($this->once())->method('getValue')->with('var name')->willReturn($actual); - $this->runner->run($command, $this->color, $this->driver); + $this->values->expects($this->once())->method('getValue')->with('var name')->willReturn($actual); + $this->runner->run($command, $this->values, $this->driver); } /** @@ -61,7 +61,7 @@ public function testAssertAlertPassed(string $alertCommand): void $locator = $this->createMock(RemoteTargetLocator::class); $locator->expects($this->once())->method('alert')->willReturn($alert); $this->driver->expects($this->once())->method('switchTo')->willReturn($locator); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } /** @@ -81,7 +81,7 @@ public function testAssertAlertFailed(string $alertCommand, string $type): void $locator = $this->createMock(RemoteTargetLocator::class); $locator->expects($this->once())->method('alert')->willReturn($alert); $this->driver->expects($this->once())->method('switchTo')->willReturn($locator); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertTitlePassed(): void @@ -90,7 +90,7 @@ public function testAssertTitlePassed(): void $command->setCommand(AssertionRunner::ASSERT_TITLE); $command->setTarget('Welcome'); $this->driver->expects($this->exactly(2))->method('getTitle')->willReturn('Welcome'); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertTitleFailed(): void @@ -101,7 +101,7 @@ public function testAssertTitleFailed(): void $command->setCommand(AssertionRunner::ASSERT_TITLE); $command->setTarget('Welcome'); $this->driver->expects($this->exactly(2))->method('getTitle')->willReturn('Goodbye'); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertTextPassed(): void @@ -117,7 +117,7 @@ public function testAssertTextPassed(): void && 'xpath' === $selector->getMechanism() && '//h4[@href="#"]' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertTextFailed(): void @@ -135,7 +135,7 @@ public function testAssertTextFailed(): void && 'xpath' === $selector->getMechanism() && '//h4[@href="#"]' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertNotTextFailed(): void @@ -154,7 +154,7 @@ public function testAssertNotTextFailed(): void && 'xpath' === $selector->getMechanism() && '//h4[@href="#"]' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertNotTextPassed(): void @@ -170,7 +170,7 @@ public function testAssertNotTextPassed(): void && 'xpath' === $selector->getMechanism() && '//h4[@href="#"]' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertValuePassed(): void @@ -186,7 +186,7 @@ public function testAssertValuePassed(): void && 'css selector' === $selector->getMechanism() && '.quality' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertValueFailed(): void @@ -204,7 +204,7 @@ public function testAssertValueFailed(): void && 'css selector' === $selector->getMechanism() && '.quality' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertEditablePassed(): void @@ -223,7 +223,7 @@ public function testAssertEditablePassed(): void ->method('executeScript') ->with('return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', [$element]) ->willReturn((object) ['enabled' => true, 'readonly' => false]); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertEditableFailed(): void @@ -244,7 +244,7 @@ public function testAssertEditableFailed(): void ->method('executeScript') ->with('return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', [$element]) ->willReturn((object) ['enabled' => false, 'readonly' => true]); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertNotEditableFailed(): void @@ -265,7 +265,7 @@ public function testAssertNotEditableFailed(): void ->method('executeScript') ->with('return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', [$element]) ->willReturn((object) ['enabled' => true, 'readonly' => false]); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertNotEditablePassed(): void @@ -284,7 +284,7 @@ public function testAssertNotEditablePassed(): void ->method('executeScript') ->with('return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', [$element]) ->willReturn((object) ['enabled' => false, 'readonly' => true]); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertElementPresentPassed(): void @@ -298,7 +298,7 @@ public function testAssertElementPresentPassed(): void && 'css selector' === $selector->getMechanism() && '.cart' === $selector->getValue(); }))->willReturn([$element]); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertElementPresentFailed(): void @@ -313,7 +313,7 @@ public function testAssertElementPresentFailed(): void && 'css selector' === $selector->getMechanism() && '.cart' === $selector->getValue(); }))->willReturn([]); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertElementNotPresentFailed(): void @@ -329,7 +329,7 @@ public function testAssertElementNotPresentFailed(): void && 'css selector' === $selector->getMechanism() && '.cart' === $selector->getValue(); }))->willReturn([$element]); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertElementNotPresentPassed(): void @@ -342,7 +342,7 @@ public function testAssertElementNotPresentPassed(): void && 'css selector' === $selector->getMechanism() && '.cart' === $selector->getValue(); }))->willReturn([]); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertCheckedPassed(): void @@ -357,7 +357,7 @@ public function testAssertCheckedPassed(): void && 'css selector' === $selector->getMechanism() && '.term-and-condition' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertCheckedFailed(): void @@ -374,7 +374,7 @@ public function testAssertCheckedFailed(): void && 'css selector' === $selector->getMechanism() && '.term-and-condition' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertNotCheckedFailed(): void @@ -391,7 +391,7 @@ public function testAssertNotCheckedFailed(): void && 'css selector' === $selector->getMechanism() && '.term-and-condition' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertNotCheckedPassed(): void @@ -406,7 +406,7 @@ public function testAssertNotCheckedPassed(): void && 'css selector' === $selector->getMechanism() && '.term-and-condition' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testAssertSelectedValuePassed(): void @@ -427,7 +427,7 @@ public function testAssertSelectedValuePassed(): void $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); $runner = $this->createPartialMock(AssertionRunner::class, ['getSelect']); $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->color, $this->driver); + $runner->run($command, $this->values, $this->driver); } public function testAssertSelectedValueFailed(): void @@ -450,7 +450,7 @@ public function testAssertSelectedValueFailed(): void $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); $runner = $this->createPartialMock(AssertionRunner::class, ['getSelect']); $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->color, $this->driver); + $runner->run($command, $this->values, $this->driver); } public function testAssertNotSelectedValueFailed(): void @@ -473,7 +473,7 @@ public function testAssertNotSelectedValueFailed(): void $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); $runner = $this->createPartialMock(AssertionRunner::class, ['getSelect']); $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->color, $this->driver); + $runner->run($command, $this->values, $this->driver); } public function testAssertNotSelectedValuePassed(): void @@ -494,7 +494,7 @@ public function testAssertNotSelectedValuePassed(): void $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); $runner = $this->createPartialMock(AssertionRunner::class, ['getSelect']); $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->color, $this->driver); + $runner->run($command, $this->values, $this->driver); } public function testAssertSelectedLabelPassed(): void @@ -515,7 +515,7 @@ public function testAssertSelectedLabelPassed(): void $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); $runner = $this->createPartialMock(AssertionRunner::class, ['getSelect']); $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->color, $this->driver); + $runner->run($command, $this->values, $this->driver); } public function testAssertSelectedLabelFailed(): void @@ -538,7 +538,7 @@ public function testAssertSelectedLabelFailed(): void $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); $runner = $this->createPartialMock(AssertionRunner::class, ['getSelect']); $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->color, $this->driver); + $runner->run($command, $this->values, $this->driver); } public function testAssertNotSelectedLabelFailed(): void @@ -561,7 +561,7 @@ public function testAssertNotSelectedLabelFailed(): void $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); $runner = $this->createPartialMock(AssertionRunner::class, ['getSelect']); $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->color, $this->driver); + $runner->run($command, $this->values, $this->driver); } public function testAssertNotSelectedLabelPassed(): void @@ -582,7 +582,7 @@ public function testAssertNotSelectedLabelPassed(): void $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); $runner = $this->createPartialMock(AssertionRunner::class, ['getSelect']); $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->color, $this->driver); + $runner->run($command, $this->values, $this->driver); } public function alertCommandProvider(): array diff --git a/tests/Command/Runner/KeyboardCommandRunnerTest.php b/tests/Command/Runner/KeyboardCommandRunnerTest.php index f8f9c84b..a475ca28 100644 --- a/tests/Command/Runner/KeyboardCommandRunnerTest.php +++ b/tests/Command/Runner/KeyboardCommandRunnerTest.php @@ -36,7 +36,7 @@ public function testType(): void && 'name' === $selector->getMechanism() && 'age' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testSendKeys(): void @@ -53,7 +53,7 @@ public function testSendKeys(): void && 'css selector' === $selector->getMechanism() && '.quantity' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function targetProvider(): array diff --git a/tests/Command/Runner/MouseCommandRunnerTest.php b/tests/Command/Runner/MouseCommandRunnerTest.php index 8f591c5f..f9762668 100644 --- a/tests/Command/Runner/MouseCommandRunnerTest.php +++ b/tests/Command/Runner/MouseCommandRunnerTest.php @@ -42,7 +42,7 @@ public function testAddSelectionByIndex(): void $select->expects($this->once())->method('selectByIndex')->with(123); $runner = $this->createPartialMock(MouseCommandRunner::class, ['getSelect']); $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->color, $this->driver); + $runner->run($command, $this->values, $this->driver); } public function testAddSelectionByValue(): void @@ -61,7 +61,7 @@ public function testAddSelectionByValue(): void $select->expects($this->once())->method('selectByValue')->with('en_GB'); $runner = $this->createPartialMock(MouseCommandRunner::class, ['getSelect']); $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->color, $this->driver); + $runner->run($command, $this->values, $this->driver); } public function testAddSelectionByLabel(): void @@ -80,7 +80,7 @@ public function testAddSelectionByLabel(): void $select->expects($this->once())->method('selectByVisibleText')->with('English (UK)'); $runner = $this->createPartialMock(MouseCommandRunner::class, ['getSelect']); $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->color, $this->driver); + $runner->run($command, $this->values, $this->driver); } public function testRemoveSelectionByIndex(): void @@ -99,7 +99,7 @@ public function testRemoveSelectionByIndex(): void $select->expects($this->once())->method('deselectByIndex')->with(123); $runner = $this->createPartialMock(MouseCommandRunner::class, ['getSelect']); $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->color, $this->driver); + $runner->run($command, $this->values, $this->driver); } public function testRemoveSelectionByValue(): void @@ -118,7 +118,7 @@ public function testRemoveSelectionByValue(): void $select->expects($this->once())->method('deselectByValue')->with('en_GB'); $runner = $this->createPartialMock(MouseCommandRunner::class, ['getSelect']); $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->color, $this->driver); + $runner->run($command, $this->values, $this->driver); } public function testRemoveSelectionByLabel(): void @@ -137,7 +137,7 @@ public function testRemoveSelectionByLabel(): void $select->expects($this->once())->method('deselectByVisibleText')->with('English (UK)'); $runner = $this->createPartialMock(MouseCommandRunner::class, ['getSelect']); $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->color, $this->driver); + $runner->run($command, $this->values, $this->driver); } /** @@ -156,7 +156,7 @@ public function testCheck(bool $selected, bool $checked): void && 'id' === $selector->getMechanism() && 'language' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } /** @@ -175,7 +175,7 @@ public function testUncheck(bool $selected, bool $unchecked): void && 'id' === $selector->getMechanism() && 'language' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testClick(): void @@ -190,7 +190,7 @@ public function testClick(): void && 'id' === $selector->getMechanism() && 'add-to-cart' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testClickAt(): void @@ -210,7 +210,7 @@ public function testClickAt(): void $action->expects($this->once())->method('click')->willReturnSelf(); $action->expects($this->once())->method('perform'); $this->driver->expects($this->once())->method('action')->willReturn($action); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testDoubleClick(): void @@ -228,7 +228,7 @@ public function testDoubleClick(): void $action->expects($this->once())->method('doubleClick')->with($element)->willReturnSelf(); $action->expects($this->once())->method('perform'); $this->driver->expects($this->once())->method('action')->willReturn($action); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testDoubleClickAt(): void @@ -248,7 +248,7 @@ public function testDoubleClickAt(): void $action->expects($this->once())->method('doubleClick')->willReturnSelf(); $action->expects($this->once())->method('perform'); $this->driver->expects($this->once())->method('action')->willReturn($action); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testDragAndDropToObject(): void @@ -278,7 +278,7 @@ public function testDragAndDropToObject(): void $action->expects($this->once())->method('dragAndDrop')->with($source, $target)->willReturnSelf(); $action->expects($this->once())->method('perform'); $this->driver->expects($this->once())->method('action')->willReturn($action); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testMouseDown(): void @@ -297,7 +297,7 @@ public function testMouseDown(): void $mouse = $this->createMock(RemoteMouse::class); $mouse->expects($this->once())->method('mouseDown')->with($coord); $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testMouseDownAt(): void @@ -318,7 +318,7 @@ public function testMouseDownAt(): void $mouse->expects($this->once())->method('mouseMove')->with($coord, 5, 10)->willReturnSelf(); $mouse->expects($this->once())->method('mouseDown'); $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testMouseMoveAt(): void @@ -338,7 +338,7 @@ public function testMouseMoveAt(): void $mouse = $this->createMock(RemoteMouse::class); $mouse->expects($this->once())->method('mouseMove')->with($coord, 5, 10); $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testMouseOutTop(): void @@ -364,7 +364,7 @@ public function testMouseOutTop(): void $rect = (object) ['top' => 1, 'height' => 2], $vp = (object) [], ]); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testMouseOutRight(): void @@ -390,7 +390,7 @@ public function testMouseOutRight(): void $rect = (object) ['top' => 0, 'right' => 2], $vp = (object) ['width' => 3], ]); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testMouseOutBottom(): void @@ -416,7 +416,7 @@ public function testMouseOutBottom(): void $rect = (object) ['top' => 0, 'right' => 2, 'bottom' => 1, 'height' => 4], $vp = (object) ['width' => 2, 'height' => 2], ]); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testMouseOutLeft(): void @@ -442,7 +442,7 @@ public function testMouseOutLeft(): void $rect = (object) ['top' => 0, 'right' => 2, 'bottom' => 1, 'left' => 1], $vp = (object) ['width' => 2, 'height' => 1], ]); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testUnableMouseOut(): void @@ -467,7 +467,7 @@ public function testUnableMouseOut(): void ]); $this->expectException(\Exception::class); $this->expectExceptionMessage('Unable to perform mouse out as the element takes up the entire viewport'); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testMouseOver(): void @@ -486,7 +486,7 @@ public function testMouseOver(): void $mouse = $this->createMock(RemoteMouse::class); $mouse->expects($this->once())->method('mouseMove')->with($coord); $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testMouseUp(): void @@ -505,7 +505,7 @@ public function testMouseUp(): void $mouse = $this->createMock(RemoteMouse::class); $mouse->expects($this->once())->method('mouseUp')->with($coord); $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testMouseUpAt(): void @@ -526,7 +526,7 @@ public function testMouseUpAt(): void $mouse->expects($this->once())->method('mouseMove')->with($coord, 5, 10)->willReturnSelf(); $mouse->expects($this->once())->method('mouseUp'); $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testSelect(): void @@ -548,7 +548,7 @@ public function testSelect(): void && 'css selector' === $selector->getMechanism() && 'option[value=en_US]' === $selector->getValue(); }))->willReturn($option); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function checkDataProvider(): array diff --git a/tests/Command/Runner/RunnerTestCase.php b/tests/Command/Runner/RunnerTestCase.php index 40917c86..be6604f3 100644 --- a/tests/Command/Runner/RunnerTestCase.php +++ b/tests/Command/Runner/RunnerTestCase.php @@ -4,21 +4,21 @@ use Facebook\WebDriver\Remote\RemoteWebDriver; use PHPUnit\Framework\TestCase; -use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Command\CommandRunner; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; use Tienvx\Bundle\MbtBundle\ValueObject\Model\Command; abstract class RunnerTestCase extends TestCase { protected RemoteWebDriver $driver; protected CommandRunner $runner; - protected ColorInterface $color; + protected ValuesInterface $values; protected function setUp(): void { $this->driver = $this->createMock(RemoteWebDriver::class); $this->runner = $this->createRunner(); - $this->color = $this->createMock(ColorInterface::class); + $this->values = $this->createMock(ValuesInterface::class); } abstract protected function createRunner(): CommandRunner; diff --git a/tests/Command/Runner/ScriptCommandRunnerTest.php b/tests/Command/Runner/ScriptCommandRunnerTest.php index 7c9651b4..b8cd47ef 100644 --- a/tests/Command/Runner/ScriptCommandRunnerTest.php +++ b/tests/Command/Runner/ScriptCommandRunnerTest.php @@ -24,12 +24,12 @@ public function testRunScript(): void $command = new Command(); $command->setCommand(ScriptCommandRunner::RUN_SCRIPT); $command->setTarget('alert("Hello World!")'); - $this->color->expects($this->never())->method('getValues'); + $this->values->expects($this->never())->method('getValues'); $this->driver->expects($this->once())->method('executeScript')->with( 'alert("Hello World!")', [], ); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testExecuteScript(): void @@ -38,13 +38,13 @@ public function testExecuteScript(): void $command->setCommand(ScriptCommandRunner::EXECUTE_SCRIPT); $command->setTarget('return 2 + 1;'); $command->setValue('total'); - $this->color->expects($this->never())->method('getValues'); - $this->color->expects($this->once())->method('setValue')->with('total', 3); + $this->values->expects($this->never())->method('getValues'); + $this->values->expects($this->once())->method('setValue')->with('total', 3); $this->driver->expects($this->once())->method('executeScript')->with( 'return 2 + 1;', [], )->willReturn(3); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testExecuteAsyncScript(): void @@ -53,13 +53,13 @@ public function testExecuteAsyncScript(): void $command->setCommand(ScriptCommandRunner::EXECUTE_ASYNC_SCRIPT); $command->setTarget('window.setTimeout(function() { return "Hello";}, 1000);'); $command->setValue('message'); - $this->color->expects($this->never())->method('getValues'); - $this->color->expects($this->once())->method('setValue')->with('message', 'Hello'); + $this->values->expects($this->never())->method('getValues'); + $this->values->expects($this->once())->method('setValue')->with('message', 'Hello'); $this->driver->expects($this->once())->method('executeAsyncScript')->with( 'window.setTimeout(function() { return "Hello";}, 1000);', [], )->willReturn('Hello'); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function targetProvider(): array diff --git a/tests/Command/Runner/StoreCommandRunnerTest.php b/tests/Command/Runner/StoreCommandRunnerTest.php index 018a48cc..0ad2034a 100644 --- a/tests/Command/Runner/StoreCommandRunnerTest.php +++ b/tests/Command/Runner/StoreCommandRunnerTest.php @@ -27,8 +27,8 @@ public function testStore(): void $command->setCommand(StoreCommandRunner::STORE); $command->setTarget('1'); $command->setValue('count'); - $this->color->expects($this->once())->method('setValue')->with('count', '1'); - $this->runner->run($command, $this->color, $this->driver); + $this->values->expects($this->once())->method('setValue')->with('count', '1'); + $this->runner->run($command, $this->values, $this->driver); } public function testStoreAttribute(): void @@ -37,7 +37,7 @@ public function testStoreAttribute(): void $command->setCommand(StoreCommandRunner::STORE_ATTRIBUTE); $command->setTarget('css=.readmore@href'); $command->setValue('readmoreLink'); - $this->color->expects($this->once())->method('setValue')->with('readmoreLink', 'http://example.com'); + $this->values->expects($this->once())->method('setValue')->with('readmoreLink', 'http://example.com'); $element = $this->createMock(WebDriverElement::class); $element->expects($this->once())->method('getAttribute')->with('href')->willReturn('http://example.com'); $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { @@ -45,7 +45,7 @@ public function testStoreAttribute(): void && 'css selector' === $selector->getMechanism() && '.readmore' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testStoreElementCount(): void @@ -54,14 +54,14 @@ public function testStoreElementCount(): void $command->setCommand(StoreCommandRunner::STORE_ELEMENT_COUNT); $command->setTarget('css=.item'); $command->setValue('itemCount'); - $this->color->expects($this->once())->method('setValue')->with('itemCount', 2); + $this->values->expects($this->once())->method('setValue')->with('itemCount', 2); $element = $this->createMock(WebDriverElement::class); $this->driver->expects($this->once())->method('findElements')->with($this->callback(function ($selector) { return $selector instanceof WebDriverBy && 'css selector' === $selector->getMechanism() && '.item' === $selector->getValue(); }))->willReturn([$element, $element]); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testStoreJson(): void @@ -70,11 +70,11 @@ public function testStoreJson(): void $command->setCommand(StoreCommandRunner::STORE_JSON); $command->setTarget('{ "items": [1, 2, 3] }'); $command->setValue('json'); - $this->color->expects($this->once())->method('setValue')->with( + $this->values->expects($this->once())->method('setValue')->with( 'json', $this->callback(fn ($object) => $object instanceof \stdClass && $object->items === [1, 2, 3]) ); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testStoreText(): void @@ -83,7 +83,7 @@ public function testStoreText(): void $command->setCommand(StoreCommandRunner::STORE_TEXT); $command->setTarget('css=.head-line'); $command->setValue('headLine'); - $this->color->expects($this->once())->method('setValue')->with('headLine', 'Welcome to our site'); + $this->values->expects($this->once())->method('setValue')->with('headLine', 'Welcome to our site'); $element = $this->createMock(WebDriverElement::class); $element->expects($this->once())->method('getText')->willReturn('Welcome to our site'); $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { @@ -91,7 +91,7 @@ public function testStoreText(): void && 'css selector' === $selector->getMechanism() && '.head-line' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testStoreTitle(): void @@ -99,9 +99,9 @@ public function testStoreTitle(): void $command = new Command(); $command->setCommand(StoreCommandRunner::STORE_TITLE); $command->setTarget('title'); - $this->color->expects($this->once())->method('setValue')->with('title', 'Welcome'); + $this->values->expects($this->once())->method('setValue')->with('title', 'Welcome'); $this->driver->expects($this->once())->method('getTitle')->willReturn('Welcome'); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testStoreValue(): void @@ -110,7 +110,7 @@ public function testStoreValue(): void $command->setCommand(StoreCommandRunner::STORE_VALUE); $command->setTarget('css=.age'); $command->setValue('age'); - $this->color->expects($this->once())->method('setValue')->with('age', 23); + $this->values->expects($this->once())->method('setValue')->with('age', 23); $element = $this->createMock(WebDriverElement::class); $element->expects($this->once())->method('getAttribute')->with('value')->willReturn(23); $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { @@ -118,7 +118,7 @@ public function testStoreValue(): void && 'css selector' === $selector->getMechanism() && '.age' === $selector->getValue(); }))->willReturn($element); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testStoreWindowHandle(): void @@ -126,9 +126,9 @@ public function testStoreWindowHandle(): void $command = new Command(); $command->setCommand(StoreCommandRunner::STORE_WINDOW_HANDLE); $command->setTarget('windowHandle'); - $this->color->expects($this->once())->method('setValue')->with('windowHandle', 'window-123'); + $this->values->expects($this->once())->method('setValue')->with('windowHandle', 'window-123'); $this->driver->expects($this->once())->method('getWindowHandle')->willReturn('window-123'); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function targetProvider(): array diff --git a/tests/Command/Runner/WaitCommandRunnerTest.php b/tests/Command/Runner/WaitCommandRunnerTest.php index 9b146f23..bed3b764 100644 --- a/tests/Command/Runner/WaitCommandRunnerTest.php +++ b/tests/Command/Runner/WaitCommandRunnerTest.php @@ -48,7 +48,7 @@ public function testWaitForElementEditable(): void ->method('executeScript') ->with('return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', [$element]) ->willReturn((object) ['enabled' => true, 'readonly' => false]); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testWaitForElementNotEditable(): void @@ -74,7 +74,7 @@ public function testWaitForElementNotEditable(): void ->method('executeScript') ->with('return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', [$element]) ->willReturn((object) ['enabled' => false, 'readonly' => true]); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testWaitForElementPresent(): void @@ -94,7 +94,7 @@ public function testWaitForElementPresent(): void && call_user_func($condition->getApply(), $this->driver); })); $this->driver->expects($this->once())->method('wait')->willReturn($wait); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testNoElementsPresent(): void @@ -108,7 +108,7 @@ public function testNoElementsPresent(): void && 'button' === $selector->getValue(); }))->willReturn([]); $this->driver->expects($this->never())->method('wait'); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testWaitForElementNotPresent(): void @@ -132,7 +132,7 @@ public function testWaitForElementNotPresent(): void && call_user_func($condition->getApply(), $this->driver); })); $this->driver->expects($this->once())->method('wait')->willReturn($wait); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testWaitForElementVisible(): void @@ -153,7 +153,7 @@ public function testWaitForElementVisible(): void && call_user_func($condition->getApply(), $this->driver); })); $this->driver->expects($this->once())->method('wait')->willReturn($wait); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testWaitForElementNotVisible(): void @@ -174,7 +174,7 @@ public function testWaitForElementNotVisible(): void && call_user_func($condition->getApply(), $this->driver); })); $this->driver->expects($this->once())->method('wait')->willReturn($wait); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function targetProvider(): array diff --git a/tests/Command/Runner/WindowCommandRunnerTest.php b/tests/Command/Runner/WindowCommandRunnerTest.php index 61ca8009..ba24e40b 100644 --- a/tests/Command/Runner/WindowCommandRunnerTest.php +++ b/tests/Command/Runner/WindowCommandRunnerTest.php @@ -33,7 +33,7 @@ public function testOpen(): void $command->setCommand(WindowCommandRunner::OPEN); $command->setTarget('https://demo.sylius.com/en_US/'); $this->driver->expects($this->once())->method('get')->with('https://demo.sylius.com/en_US/'); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testSetWindowSize(): void @@ -50,7 +50,7 @@ public function testSetWindowSize(): void $options = $this->createMock(WebDriverOptions::class); $options->expects($this->once())->method('window')->willReturn($window); $this->driver->expects($this->once())->method('manage')->willReturn($options); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testSelectWindow(): void @@ -61,7 +61,7 @@ public function testSelectWindow(): void $targetLocator = $this->createMock(RemoteTargetLocator::class); $targetLocator->expects($this->once())->method('window')->with('testing'); $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testClose(): void @@ -69,7 +69,7 @@ public function testClose(): void $command = new Command(); $command->setCommand(WindowCommandRunner::CLOSE); $this->driver->expects($this->once())->method('close'); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testSelectFrameRelativeTop(): void @@ -80,7 +80,7 @@ public function testSelectFrameRelativeTop(): void $targetLocator = $this->createMock(RemoteTargetLocator::class); $targetLocator->expects($this->once())->method('defaultContent'); $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testSelectFrameRelativeParent(): void @@ -91,7 +91,7 @@ public function testSelectFrameRelativeParent(): void $targetLocator = $this->createMock(RemoteTargetLocator::class); $targetLocator->expects($this->once())->method('parent'); $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testSelectFrameIndex(): void @@ -102,7 +102,7 @@ public function testSelectFrameIndex(): void $targetLocator = $this->createMock(RemoteTargetLocator::class); $targetLocator->expects($this->once())->method('frame')->with(123); $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function testSelectFrameSelector(): void @@ -125,7 +125,7 @@ public function testSelectFrameSelector(): void && call_user_func($condition->getApply(), $this->driver); })); $this->driver->expects($this->once())->method('wait')->willReturn($wait); - $this->runner->run($command, $this->color, $this->driver); + $this->runner->run($command, $this->values, $this->driver); } public function targetProvider(): array diff --git a/tests/Model/Model/Revision/TransitionTest.php b/tests/Model/Model/Revision/TransitionTest.php index 4c728355..a1a945a2 100644 --- a/tests/Model/Model/Revision/TransitionTest.php +++ b/tests/Model/Model/Revision/TransitionTest.php @@ -29,6 +29,7 @@ protected function setUp(): void $this->transition = $this->createTransition(); $this->transition->setLabel('transition label'); $this->transition->setGuard('count > 2'); + $this->transition->setExpression('{count: count + 1}'); $this->transition->setFromPlaces([1, 2, 3]); $this->transition->setToPlaces([12, 23]); $this->transition->setCommands([ @@ -60,17 +61,18 @@ public function testSerialize(): void { $className = get_class($this->transition); // phpcs:ignore Generic.Files.LineLength - $this->assertSame('O:' . strlen($className) . ':"' . $className . '":5:{s:5:"label";s:16:"transition label";s:5:"guard";s:9:"count > 2";s:10:"fromPlaces";a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}s:8:"toPlaces";a:2:{i:0;i:12;i:1;i:23;}s:8:"commands";a:2:{i:0;a:3:{s:7:"command";s:4:"type";s:6:"target";s:10:"css=.email";s:5:"value";s:16:"test@example.com";}i:1;a:3:{s:7:"command";s:5:"click";s:6:"target";s:9:"css=.link";s:5:"value";N;}}}', serialize($this->transition)); + $this->assertSame('O:' . strlen($className) . ':"' . $className . '":6:{s:5:"label";s:16:"transition label";s:5:"guard";s:9:"count > 2";s:10:"expression";s:18:"{count: count + 1}";s:10:"fromPlaces";a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}s:8:"toPlaces";a:2:{i:0;i:12;i:1;i:23;}s:8:"commands";a:2:{i:0;a:3:{s:7:"command";s:4:"type";s:6:"target";s:10:"css=.email";s:5:"value";s:16:"test@example.com";}i:1;a:3:{s:7:"command";s:5:"click";s:6:"target";s:9:"css=.link";s:5:"value";N;}}}', serialize($this->transition)); } public function testUnerialize(): void { $className = get_class($this->transition); // phpcs:ignore Generic.Files.LineLength - $transition = unserialize('O:' . strlen($className) . ':"' . $className . '":5:{s:5:"label";s:10:"Serialized";s:5:"guard";s:10:"count == 3";s:10:"fromPlaces";a:2:{i:0;i:1;i:1;i:4;}s:8:"toPlaces";a:1:{i:0;i:15;}s:8:"commands";a:1:{i:0;a:3:{s:7:"command";s:5:"store";s:6:"target";s:2:"55";s:5:"value";s:6:"number";}}}'); + $transition = unserialize('O:' . strlen($className) . ':"' . $className . '":6:{s:5:"label";s:10:"Serialized";s:5:"guard";s:10:"count == 3";s:10:"expression";s:18:"{count: count - 1}";s:10:"fromPlaces";a:2:{i:0;i:1;i:1;i:4;}s:8:"toPlaces";a:1:{i:0;i:15;}s:8:"commands";a:1:{i:0;a:3:{s:7:"command";s:5:"store";s:6:"target";s:2:"55";s:5:"value";s:6:"number";}}}'); $this->assertInstanceOf(TransitionInterface::class, $transition); $this->assertSame('Serialized', $transition->getLabel()); $this->assertSame('count == 3', $transition->getGuard()); + $this->assertSame('{count: count - 1}', $transition->getExpression()); $this->assertSame([1, 4], $transition->getFromPlaces()); $this->assertSame([15], $transition->getToPlaces()); $this->assertInstanceOf(CommandInterface::class, $transition->getCommands()[0]); diff --git a/tests/Model/Model/RevisionTest.php b/tests/Model/Model/RevisionTest.php index 93994025..aca7e1ce 100644 --- a/tests/Model/Model/RevisionTest.php +++ b/tests/Model/Model/RevisionTest.php @@ -70,6 +70,7 @@ protected function setUp(): void $t1->setLabel('t1'); $t1->setFromPlaces([1]); $t1->setToPlaces([1, 2]); + $t1->setExpression('{count: count + 1}'); $t2->setLabel(''); $t2->setFromPlaces([1, 2]); $t2->setToPlaces([]); @@ -143,6 +144,7 @@ public function testToArray(): void 0 => [ 'label' => 't1', 'guard' => null, + 'expression' => '{count: count + 1}', 'fromPlaces' => [ 0 => 1, ], @@ -156,6 +158,7 @@ public function testToArray(): void 1 => [ 'label' => '', 'guard' => 'count > 1', + 'expression' => null, 'fromPlaces' => [ 0 => 1, 1 => 2, diff --git a/tests/Model/ValuesTest.php b/tests/Model/ValuesTest.php new file mode 100644 index 00000000..cda452bc --- /dev/null +++ b/tests/Model/ValuesTest.php @@ -0,0 +1,39 @@ + 'value', 'key2' => 'value2']); + $this->assertSame(['key' => 'value', 'key2' => 'value2'], $values->getValues()); + } + + public function testGetValue(): void + { + $values = new Values(['key' => 'value']); + $this->assertSame('value', $values->getValue('key')); + $this->assertNull($values->getValue('key2')); + } + + public function testSetValue(): void + { + $values = new Values(); + $this->assertSame([], $values->getValues()); + $values->setValue('key', 'value'); + $values->setValue('key2', null); + $values->setValue('key3', 123); + $values->setValue('key4', json_decode('{"test1":"Test 1","test2":{},"test3":{"test4":"Test 4"}}')); + $this->assertSame( + '{"key":"value","key2":null,"key3":123,"key4":{"test1":"Test 1","test2":{},"test3":{"test4":"Test 4"}}}', + json_encode($values->getValues()) + ); + } +} diff --git a/tests/Service/Petrinet/PetrinetHelperTest.php b/tests/Service/Petrinet/PetrinetHelperTest.php index 3676c23a..4e1b5e69 100644 --- a/tests/Service/Petrinet/PetrinetHelperTest.php +++ b/tests/Service/Petrinet/PetrinetHelperTest.php @@ -50,6 +50,7 @@ public function testBuild(): void $transition1->setToPlaces([1, 2]); $transition2->setFromPlaces([2]); $transition2->setToPlaces([1]); + $transition3->setExpression('{count: count + 1, status: "open"}'); $transition3->setFromPlaces([1]); $transition3->setToPlaces([0, 2]); @@ -113,6 +114,13 @@ public function testBuild(): void } else { $this->assertNull($transition->getGuard()); } + if (2 === $index) { + $this->assertIsCallable($expressionCallback = $transition->getExpression()); + $this->assertSame(['count' => 2, 'status' => 'open'], $expressionCallback(new Color(['count' => 1]))); + $this->assertSame(['count' => 1, 'status' => 'open'], $expressionCallback(new Color(['count' => 0]))); + } else { + $this->assertNull($transition->getExpression()); + } } } } diff --git a/tests/Service/Step/Runner/StepRunnerTest.php b/tests/Service/Step/Runner/StepRunnerTest.php index ef709f5e..e43c3bf1 100644 --- a/tests/Service/Step/Runner/StepRunnerTest.php +++ b/tests/Service/Step/Runner/StepRunnerTest.php @@ -12,6 +12,7 @@ use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; use Tienvx\Bundle\MbtBundle\Factory\Model\Revision\CommandFactory; use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepRunner; use Tienvx\Bundle\MbtBundle\ValueObject\Bug\Step; use Tienvx\Bundle\MbtBundle\ValueObject\Model\Place; @@ -26,6 +27,7 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision\Command * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision * @uses \Tienvx\Bundle\MbtBundle\Factory\Model\Revision\CommandFactory + * @uses \Tienvx\Bundle\MbtBundle\Model\Values */ class StepRunnerTest extends TestCase { @@ -34,6 +36,7 @@ class StepRunnerTest extends TestCase protected CommandRunnerManager $commandRunnerManager; protected RemoteWebDriver $driver; protected ColorInterface $color; + protected array $valuesInstances = []; protected function setUp(): void { @@ -60,12 +63,19 @@ protected function setUp(): void $command5 = CommandFactory::create(AssertionRunner::ASSERT_TEXT), ]); $this->revision->setPlaces($places); + $assertValuesInstance = function (ValuesInterface $values) { + if (!in_array($values, $this->valuesInstances, true)) { + $this->valuesInstances[] = $values; + } + + return true; + }; $this->commands = [ - [$command1, $this->color, $this->driver], - [$command2, $this->color, $this->driver], - [$command3, $this->color, $this->driver], - [$command4, $this->color, $this->driver], - [$command5, $this->color, $this->driver], + [$command1, $this->callback($assertValuesInstance), $this->driver], + [$command2, $this->callback($assertValuesInstance), $this->driver], + [$command3, $this->callback($assertValuesInstance), $this->driver], + [$command4, $this->callback($assertValuesInstance), $this->driver], + [$command5, $this->callback($assertValuesInstance), $this->driver], ]; $this->commandRunnerManager = $this->createMock(CommandRunnerManager::class); @@ -77,5 +87,6 @@ public function testRun(): void $this->commandRunnerManager->expects($this->exactly(5))->method('run')->withConsecutive(...$this->commands); $stepRunner = new StepRunner($this->commandRunnerManager); $stepRunner->run($step, $this->revision, $this->driver); + $this->assertCount(3, $this->valuesInstances); } } From 016d8689e5d8d4e40ecdbd97476962338367c4d1 Mon Sep 17 00:00:00 2001 From: tienvx Date: Wed, 27 Apr 2022 23:11:43 +0700 Subject: [PATCH 45/85] Stop running task or recording bug if something wrong happen --- src/Service/Bug/BugHelper.php | 27 +++++++++++++++------------ src/Service/Task/TaskHelper.php | 24 +++++++++++++----------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/Service/Bug/BugHelper.php b/src/Service/Bug/BugHelper.php index a78df9f5..665b7c2d 100644 --- a/src/Service/Bug/BugHelper.php +++ b/src/Service/Bug/BugHelper.php @@ -97,18 +97,21 @@ public function recordVideo(int $bugId): void ); } - $this->bugRepository->startRecording($bug); - $bug->setDebug(true); - $this->stepsRunner->run( - $this->stepHelper->cloneAndResetSteps($bug->getSteps(), $bug->getTask()->getModelRevision()), - $bug, - function (Throwable $throwable) use ($bug) { - $bug->getVideo()->setErrorMessage( - $throwable->getMessage() !== $bug->getMessage() ? $throwable->getMessage() : null - ); - } - ); - $this->bugRepository->stopRecording($bug); + try { + $this->bugRepository->startRecording($bug); + $bug->setDebug(true); + $this->stepsRunner->run( + $this->stepHelper->cloneAndResetSteps($bug->getSteps(), $bug->getTask()->getModelRevision()), + $bug, + function (Throwable $throwable) use ($bug) { + $bug->getVideo()->setErrorMessage( + $throwable->getMessage() !== $bug->getMessage() ? $throwable->getMessage() : null + ); + } + ); + } finally { + $this->bugRepository->stopRecording($bug); + } } protected function getBug(int $bugId, string $action): BugInterface diff --git a/src/Service/Task/TaskHelper.php b/src/Service/Task/TaskHelper.php index 176bc046..55a65cf3 100644 --- a/src/Service/Task/TaskHelper.php +++ b/src/Service/Task/TaskHelper.php @@ -44,16 +44,18 @@ public function run(int $taskId): void ); } - $this->taskRepository->startRunning($task); - - $this->stepsRunner->run( - $this->generatorManager->getGenerator($this->config->getGenerator())->generate($task), - $task, - function (BugInterface $bug) use ($task) { - $task->addBug($bug); - } - ); - - $this->taskRepository->stopRunning($task); + try { + $this->taskRepository->startRunning($task); + + $this->stepsRunner->run( + $this->generatorManager->getGenerator($this->config->getGenerator())->generate($task), + $task, + function (BugInterface $bug) use ($task) { + $task->addBug($bug); + } + ); + } finally { + $this->taskRepository->stopRunning($task); + } } } From 952ed758b1571def6777d60f3ea97b9f9d585417 Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 29 Apr 2022 00:28:25 +0700 Subject: [PATCH 46/85] Fire start transition --- composer.json | 2 +- src/Generator/RandomGenerator.php | 26 ++++----- src/Service/AStar/PetrinetDomainLogic.php | 2 +- .../AStar/PetrinetDomainLogicInterface.php | 2 +- src/Service/Petrinet/MarkingHelper.php | 4 +- .../Petrinet/MarkingHelperInterface.php | 2 +- src/Service/Petrinet/PetrinetHelper.php | 55 +++++++++++-------- .../Petrinet/PetrinetHelperInterface.php | 2 +- src/Service/Step/Runner/ReduceStepsRunner.php | 4 +- .../Service/AStar/PetrinetDomainLogicTest.php | 2 +- tests/Service/Petrinet/MarkingHelperTest.php | 10 ++-- tests/Service/Petrinet/PetrinetHelperTest.php | 36 +++++++++--- .../Builder/ShortestPathStepsBuilderTest.php | 2 +- .../Step/Runner/ReduceStepsRunnerTest.php | 16 +++--- 14 files changed, 93 insertions(+), 72 deletions(-) diff --git a/composer.json b/composer.json index f52c57d7..5f702cf2 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "symfony/http-kernel": "^5.4", "symfony/messenger": "^5.4", "symfony/validator": "^5.4", - "tienvx/single-color-petrinet": "^1.3" + "tienvx/single-color-petrinet": "^1.5" }, "require-dev": { "phpunit/phpunit": "^9.5" diff --git a/src/Generator/RandomGenerator.php b/src/Generator/RandomGenerator.php index 1e620814..94269c4f 100644 --- a/src/Generator/RandomGenerator.php +++ b/src/Generator/RandomGenerator.php @@ -3,14 +3,15 @@ namespace Tienvx\Bundle\MbtBundle\Generator; use Petrinet\Model\MarkingInterface; -use Petrinet\Model\PetrinetInterface; use Petrinet\Model\TransitionInterface; +use SingleColorPetrinet\Model\PetrinetInterface; use SingleColorPetrinet\Service\GuardedTransitionServiceInterface; use Tienvx\Bundle\MbtBundle\Model\Generator\State; use Tienvx\Bundle\MbtBundle\Model\Generator\StateInterface; use Tienvx\Bundle\MbtBundle\Model\TaskInterface; use Tienvx\Bundle\MbtBundle\Service\Model\ModelHelperInterface; use Tienvx\Bundle\MbtBundle\Service\Petrinet\MarkingHelperInterface; +use Tienvx\Bundle\MbtBundle\Service\Petrinet\PetrinetHelper; use Tienvx\Bundle\MbtBundle\Service\Petrinet\PetrinetHelperInterface; use Tienvx\Bundle\MbtBundle\ValueObject\Bug\Step; @@ -44,36 +45,31 @@ public static function getName(): string public function generate(TaskInterface $task): iterable { $petrinet = $this->petrinetHelper->build($task->getModelRevision()); - $transition = null; - $transitionId = $this->modelHelper->getStartTransitionId($task->getModelRevision()); - $places = $this->modelHelper->getStartPlaceIds($task->getModelRevision()); - $marking = $this->markingHelper->getMarking($petrinet, $places); + $transition = $petrinet->getTransitionById( + $this->modelHelper->getStartTransitionId($task->getModelRevision()) + ); + $marking = $this->markingHelper->getMarking($petrinet, [PetrinetHelper::FAKE_START_PLACE_ID => 1]); $state = new State( [], - [$transitionId], + [], count($task->getModelRevision()->getPlaces()), count($task->getModelRevision()->getTransitions()) ); while ($this->canContinue($state)) { - if ($transition) { - $this->transitionService->fire($transition, $marking); - $places = $this->markingHelper->getPlaces($marking); - } - $this->update($state, $marking, $transitionId); + $this->transitionService->fire($transition, $marking); + $this->update($state, $marking, $transition->getId()); yield new Step( - $places, + $this->markingHelper->getPlaces($marking), $marking->getColor(), - $transitionId + $transition->getId() ); $transition = $this->nextTransition($petrinet, $marking); if (!$transition) { break; } - - $transitionId = $transition->getId(); } } diff --git a/src/Service/AStar/PetrinetDomainLogic.php b/src/Service/AStar/PetrinetDomainLogic.php index be6333fd..4be6ab29 100644 --- a/src/Service/AStar/PetrinetDomainLogic.php +++ b/src/Service/AStar/PetrinetDomainLogic.php @@ -2,8 +2,8 @@ namespace Tienvx\Bundle\MbtBundle\Service\AStar; -use Petrinet\Model\PetrinetInterface; use Petrinet\Model\TransitionInterface; +use SingleColorPetrinet\Model\PetrinetInterface; use SingleColorPetrinet\Service\GuardedTransitionServiceInterface; use Tienvx\Bundle\MbtBundle\Exception\RuntimeException; use Tienvx\Bundle\MbtBundle\Model\Bug\Step; diff --git a/src/Service/AStar/PetrinetDomainLogicInterface.php b/src/Service/AStar/PetrinetDomainLogicInterface.php index 37b59482..1df6aecc 100644 --- a/src/Service/AStar/PetrinetDomainLogicInterface.php +++ b/src/Service/AStar/PetrinetDomainLogicInterface.php @@ -3,7 +3,7 @@ namespace Tienvx\Bundle\MbtBundle\Service\AStar; use JMGQ\AStar\DomainLogicInterface; -use Petrinet\Model\PetrinetInterface; +use SingleColorPetrinet\Model\PetrinetInterface; interface PetrinetDomainLogicInterface extends DomainLogicInterface { diff --git a/src/Service/Petrinet/MarkingHelper.php b/src/Service/Petrinet/MarkingHelper.php index 806f4d38..2a5597c6 100644 --- a/src/Service/Petrinet/MarkingHelper.php +++ b/src/Service/Petrinet/MarkingHelper.php @@ -4,11 +4,11 @@ use Petrinet\Builder\MarkingBuilder; use Petrinet\Model\MarkingInterface; -use Petrinet\Model\PetrinetInterface; use Petrinet\Model\PlaceMarkingInterface; use SingleColorPetrinet\Model\ColorfulFactoryInterface; use SingleColorPetrinet\Model\ColorfulMarkingInterface; use SingleColorPetrinet\Model\ColorInterface; +use SingleColorPetrinet\Model\PetrinetInterface; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; class MarkingHelper implements MarkingHelperInterface @@ -45,7 +45,7 @@ public function getMarking( ): ColorfulMarkingInterface { $markingBuilder = new MarkingBuilder($this->colorfulFactory); foreach ($places as $place => $tokens) { - $markingBuilder->mark($petrinet->getPlaces()[$place], $tokens); + $markingBuilder->mark($petrinet->getPlaceById($place), $tokens); } $marking = $markingBuilder->getMarking(); diff --git a/src/Service/Petrinet/MarkingHelperInterface.php b/src/Service/Petrinet/MarkingHelperInterface.php index 153b675a..bc6a7556 100644 --- a/src/Service/Petrinet/MarkingHelperInterface.php +++ b/src/Service/Petrinet/MarkingHelperInterface.php @@ -3,9 +3,9 @@ namespace Tienvx\Bundle\MbtBundle\Service\Petrinet; use Petrinet\Model\MarkingInterface; -use Petrinet\Model\PetrinetInterface; use SingleColorPetrinet\Model\ColorfulMarkingInterface; use SingleColorPetrinet\Model\ColorInterface; +use SingleColorPetrinet\Model\PetrinetInterface; interface MarkingHelperInterface { diff --git a/src/Service/Petrinet/PetrinetHelper.php b/src/Service/Petrinet/PetrinetHelper.php index b8c3b246..5da51f00 100644 --- a/src/Service/Petrinet/PetrinetHelper.php +++ b/src/Service/Petrinet/PetrinetHelper.php @@ -2,12 +2,12 @@ namespace Tienvx\Bundle\MbtBundle\Service\Petrinet; -use Petrinet\Model\PetrinetInterface; use Petrinet\Model\PlaceInterface as PetrinetPlaceInterface; use Petrinet\Model\TransitionInterface as PetrinetTransitionInterface; use SingleColorPetrinet\Builder\SingleColorPetrinetBuilder; use SingleColorPetrinet\Model\ColorfulFactoryInterface; use SingleColorPetrinet\Model\ColorInterface; +use SingleColorPetrinet\Model\PetrinetInterface; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\PlaceInterface; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\TransitionInterface; use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; @@ -15,6 +15,8 @@ class PetrinetHelper implements PetrinetHelperInterface { + public const FAKE_START_PLACE_ID = -1; + protected ColorfulFactoryInterface $colorfulFactory; protected ExpressionLanguage $expressionLanguage; @@ -30,12 +32,16 @@ public function build(RevisionInterface $revision): PetrinetInterface $places = $this->getPlaces($revision, $builder); $transitions = $this->getTransitions($revision, $builder); foreach ($revision->getTransitions() as $index => $transition) { - if ($transition instanceof TransitionInterface && !$transition->isStart()) { - $this->connectPlacesToTransition( - array_intersect_key($places, array_flip($transition->getFromPlaces())), - $transitions[$index], - $builder - ); + if ($transition instanceof TransitionInterface) { + if ($transition->isStart()) { + $builder->connect($places[self::FAKE_START_PLACE_ID], $transitions[$index], 1); + } else { + $this->connectPlacesToTransition( + array_intersect_key($places, array_flip($transition->getFromPlaces())), + $transitions[$index], + $builder + ); + } $this->connectTransitionToPlaces($transitions[$index], $transition->getToPlaces(), $places, $builder); } } @@ -46,10 +52,10 @@ public function build(RevisionInterface $revision): PetrinetInterface protected function getPlaces(RevisionInterface $revision, SingleColorPetrinetBuilder $builder): array { $places = []; + $places[self::FAKE_START_PLACE_ID] = $builder->place(self::FAKE_START_PLACE_ID); foreach ($revision->getPlaces() as $index => $place) { if ($place instanceof PlaceInterface) { - $places[$index] = $builder->place(); - $places[$index]->setId($index); + $places[$index] = $builder->place($index); } } @@ -60,21 +66,22 @@ protected function getTransitions(RevisionInterface $revision, SingleColorPetrin { $transitions = []; foreach ($revision->getTransitions() as $index => $transition) { - if ($transition instanceof TransitionInterface && !$transition->isStart()) { - $guardCallback = $transition->getGuard() - ? fn (ColorInterface $color): bool => (bool) $this->expressionLanguage->evaluate( - $transition->getGuard(), - $color->getValues() - ) - : null; - $expressionCallback = $transition->getExpression() - ? fn (ColorInterface $color): array => (array) $this->expressionLanguage->evaluate( - $transition->getExpression(), - $color->getValues() - ) - : null; - $transitions[$index] = $builder->transition($guardCallback, $expressionCallback); - $transitions[$index]->setId($index); + if ($transition instanceof TransitionInterface) { + $transitions[$index] = $builder->transition( + $transition->getGuard() + ? fn (ColorInterface $color): bool => (bool) $this->expressionLanguage->evaluate( + $transition->getGuard(), + $color->getValues() + ) + : null, + $transition->getExpression() + ? fn (ColorInterface $color): array => (array) $this->expressionLanguage->evaluate( + $transition->getExpression(), + $color->getValues() + ) + : null, + $index + ); } } diff --git a/src/Service/Petrinet/PetrinetHelperInterface.php b/src/Service/Petrinet/PetrinetHelperInterface.php index 8bcd4b35..e1c51196 100644 --- a/src/Service/Petrinet/PetrinetHelperInterface.php +++ b/src/Service/Petrinet/PetrinetHelperInterface.php @@ -2,7 +2,7 @@ namespace Tienvx\Bundle\MbtBundle\Service\Petrinet; -use Petrinet\Model\PetrinetInterface; +use SingleColorPetrinet\Model\PetrinetInterface; use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; interface PetrinetHelperInterface diff --git a/src/Service/Step/Runner/ReduceStepsRunner.php b/src/Service/Step/Runner/ReduceStepsRunner.php index 9cb2068c..c938f337 100644 --- a/src/Service/Step/Runner/ReduceStepsRunner.php +++ b/src/Service/Step/Runner/ReduceStepsRunner.php @@ -3,7 +3,7 @@ namespace Tienvx\Bundle\MbtBundle\Service\Step\Runner; use Facebook\WebDriver\Remote\RemoteWebDriver; -use Petrinet\Model\PetrinetInterface; +use SingleColorPetrinet\Model\PetrinetInterface; use SingleColorPetrinet\Service\GuardedTransitionServiceInterface; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; use Tienvx\Bundle\MbtBundle\Model\DebugInterface; @@ -48,7 +48,7 @@ protected function stop(?RemoteWebDriver $driver): void protected function runStep(StepInterface $step, RevisionInterface $revision, RemoteWebDriver $driver): bool { $marking = $this->markingHelper->getMarking($this->petrinet, $step->getPlaces(), $step->getColor()); - $transition = $this->petrinet->getTransitions()[$step->getTransition()]; + $transition = $this->petrinet->getTransitionById($step->getTransition()); if ($this->transitionService->isEnabled($transition, $marking)) { return parent::runStep($step, $revision, $driver); } diff --git a/tests/Service/AStar/PetrinetDomainLogicTest.php b/tests/Service/AStar/PetrinetDomainLogicTest.php index a076cbc5..509d1a28 100644 --- a/tests/Service/AStar/PetrinetDomainLogicTest.php +++ b/tests/Service/AStar/PetrinetDomainLogicTest.php @@ -3,12 +3,12 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service\AStar; use Petrinet\Model\MarkingInterface; -use Petrinet\Model\PetrinetInterface; use PHPUnit\Framework\TestCase; use SingleColorPetrinet\Model\Color; use SingleColorPetrinet\Model\ColorfulMarking; use SingleColorPetrinet\Model\ColorInterface; use SingleColorPetrinet\Model\GuardedTransition; +use SingleColorPetrinet\Model\PetrinetInterface; use SingleColorPetrinet\Service\GuardedTransitionServiceInterface; use Tienvx\Bundle\MbtBundle\Exception\RuntimeException; use Tienvx\Bundle\MbtBundle\Model\Bug\Step; diff --git a/tests/Service/Petrinet/MarkingHelperTest.php b/tests/Service/Petrinet/MarkingHelperTest.php index fcaa894a..dfca208c 100644 --- a/tests/Service/Petrinet/MarkingHelperTest.php +++ b/tests/Service/Petrinet/MarkingHelperTest.php @@ -54,13 +54,13 @@ public function testGetMarking(): void $builder = new SingleColorPetrinetBuilder($this->factory); $petrinet = $builder - ->connect($place1 = $builder->place(), $transition1 = $builder->transition()) - ->connect($transition1, $place2 = $builder->place()) - ->connect($place3 = $builder->place(), $transition2 = $builder->transition()) - ->connect($transition2, $place4 = $builder->place()) + ->connect($place1 = $builder->place(1), $transition1 = $builder->transition()) + ->connect($transition1, $place2 = $builder->place(2)) + ->connect($place3 = $builder->place(3), $transition2 = $builder->transition()) + ->connect($transition2, $place4 = $builder->place(4)) ->getPetrinet(); - $marking = $this->helper->getMarking($petrinet, [0 => 1, 2 => 3], new Color(['key' => 'value'])); + $marking = $this->helper->getMarking($petrinet, [1 => 1, 3 => 3], new Color(['key' => 'value'])); $this->assertSame(['key' => 'value'], $marking->getColor()->getValues()); $this->assertCount(1, $marking->getPlaceMarking($place1)->getTokens()); $this->assertNull($marking->getPlaceMarking($place2)); diff --git a/tests/Service/Petrinet/PetrinetHelperTest.php b/tests/Service/Petrinet/PetrinetHelperTest.php index 4e1b5e69..6115a2f7 100644 --- a/tests/Service/Petrinet/PetrinetHelperTest.php +++ b/tests/Service/Petrinet/PetrinetHelperTest.php @@ -56,23 +56,32 @@ public function testBuild(): void // Petrinet $petrinet = $helper->build($revision); - $this->assertCount(3, $petrinet->getPlaces()); + $this->assertCount(4, $petrinet->getPlaces()); $places = [ 0 => [ - 'input' => [3], - 'output' => [1], + 'id' => -1, + 'input' => [], + 'output' => [0], ], 1 => [ + 'id' => 0, + 'input' => [0, 3], + 'output' => [1], + ], + 2 => [ + 'id' => 1, 'input' => [1, 2], 'output' => [3], ], - 2 => [ + 3 => [ + 'id' => 2, 'input' => [1, 3], 'output' => [2], ], ]; foreach ($petrinet->getPlaces() as $index => $place) { $this->assertInstanceOf(PetrinetPlace::class, $place); + $this->assertSame($places[$index]['id'], $place->getId()); $this->assertSame( $places[$index]['input'], array_map(fn (ArcInterface $arc) => $arc->getTransition()->getId(), $place->getInputArcs()->toArray()), @@ -82,23 +91,32 @@ public function testBuild(): void array_map(fn (ArcInterface $arc) => $arc->getTransition()->getId(), $place->getOutputArcs()->toArray()), ); } - $this->assertCount(3, $petrinet->getTransitions()); + $this->assertCount(4, $petrinet->getTransitions()); $transitions = [ 0 => [ + 'id' => 0, + 'input' => [-1], + 'output' => [0], + ], + 1 => [ + 'id' => 1, 'input' => [0], 'output' => [1, 2], ], - 1 => [ + 2 => [ + 'id' => 2, 'input' => [2], 'output' => [1], ], - 2 => [ + 3 => [ + 'id' => 3, 'input' => [1], 'output' => [0, 2], ], ]; foreach ($petrinet->getTransitions() as $index => $transition) { $this->assertInstanceOf(PetrinetTransition::class, $transition); + $this->assertSame($transitions[$index]['id'], $transition->getId()); $this->assertSame( $transitions[$index]['input'], array_map(fn (ArcInterface $arc) => $arc->getPlace()->getId(), $transition->getInputArcs()->toArray()), @@ -107,14 +125,14 @@ public function testBuild(): void $transitions[$index]['output'], array_map(fn (ArcInterface $arc) => $arc->getPlace()->getId(), $transition->getOutputArcs()->toArray()), ); - if (0 === $index) { + if (1 === $index) { $this->assertIsCallable($guardCallback = $transition->getGuard()); $this->assertTrue($guardCallback(new Color(['count' => 1]))); $this->assertFalse($guardCallback(new Color(['count' => 0]))); } else { $this->assertNull($transition->getGuard()); } - if (2 === $index) { + if (3 === $index) { $this->assertIsCallable($expressionCallback = $transition->getExpression()); $this->assertSame(['count' => 2, 'status' => 'open'], $expressionCallback(new Color(['count' => 1]))); $this->assertSame(['count' => 1, 'status' => 'open'], $expressionCallback(new Color(['count' => 0]))); diff --git a/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php b/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php index 81af963f..7ffbf068 100644 --- a/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php +++ b/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php @@ -3,7 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service\Step\Builder; use Generator; -use Petrinet\Model\PetrinetInterface; use Petrinet\Model\PlaceInterface; use Petrinet\Model\TransitionInterface; use PHPUnit\Framework\MockObject\MockObject; @@ -13,6 +12,7 @@ use SingleColorPetrinet\Model\ColorfulFactory; use SingleColorPetrinet\Model\ColorfulFactoryInterface; use SingleColorPetrinet\Model\ColorInterface; +use SingleColorPetrinet\Model\PetrinetInterface; use SingleColorPetrinet\Service\GuardedTransitionService; use SingleColorPetrinet\Service\GuardedTransitionServiceInterface; use Tienvx\Bundle\MbtBundle\Entity\Bug; diff --git a/tests/Service/Step/Runner/ReduceStepsRunnerTest.php b/tests/Service/Step/Runner/ReduceStepsRunnerTest.php index 274fdeb2..45b5fcb9 100644 --- a/tests/Service/Step/Runner/ReduceStepsRunnerTest.php +++ b/tests/Service/Step/Runner/ReduceStepsRunnerTest.php @@ -3,9 +3,9 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service\Step\Runner; use Exception; -use Petrinet\Model\PetrinetInterface; -use Petrinet\Model\TransitionInterface; use SingleColorPetrinet\Model\ColorfulMarkingInterface; +use SingleColorPetrinet\Model\GuardedTransitionInterface; +use SingleColorPetrinet\Model\PetrinetInterface; use SingleColorPetrinet\Service\GuardedTransitionServiceInterface; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; use Tienvx\Bundle\MbtBundle\Model\DebugInterface; @@ -56,10 +56,10 @@ protected function setUp(): void ->willReturn($this->petrinet); $this->marking = $this->createMock(ColorfulMarkingInterface::class); $this->transitions = [ - 0 => $this->createMock(TransitionInterface::class), - 1 => $this->createMock(TransitionInterface::class), - 2 => $this->createMock(TransitionInterface::class), - 3 => $this->createMock(TransitionInterface::class), + 0 => $this->createMock(GuardedTransitionInterface::class), + 1 => $this->createMock(GuardedTransitionInterface::class), + 2 => $this->createMock(GuardedTransitionInterface::class), + 3 => $this->createMock(GuardedTransitionInterface::class), ]; } @@ -95,8 +95,8 @@ protected function assertRunSteps( ->willReturn($this->marking); $this->petrinet ->expects($this->exactly(count($steps) + $nextStepDisabled)) - ->method('getTransitions') - ->willReturn($this->transitions); + ->method('getTransitionById') + ->willReturnCallback(fn (int $id) => $this->transitions[$id]); if (count($steps) < count($this->steps)) { $this->transitionService ->expects($this->exactly(count($steps) + $nextStepDisabled)) From 91e1b37364f41c9a02d3619e5af2fcc095933419 Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 29 Apr 2022 22:12:29 +0700 Subject: [PATCH 47/85] Fix missing expression on import --- src/Factory/Model/Revision/TransitionFactory.php | 1 + tests/Factory/Model/Revision/TransitionFactoryTest.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/Factory/Model/Revision/TransitionFactory.php b/src/Factory/Model/Revision/TransitionFactory.php index 90eddd7a..ca89d9bd 100644 --- a/src/Factory/Model/Revision/TransitionFactory.php +++ b/src/Factory/Model/Revision/TransitionFactory.php @@ -12,6 +12,7 @@ public static function createFromArray(array $data): TransitionInterface $transition = new Transition(); $transition->setLabel($data['label'] ?? ''); $transition->setGuard($data['guard'] ?? null); + $transition->setExpression($data['expression'] ?? null); $transition->setCommands( array_map([CommandFactory::class, 'createFromArray'], ($data['commands'] ?? [])) ); diff --git a/tests/Factory/Model/Revision/TransitionFactoryTest.php b/tests/Factory/Model/Revision/TransitionFactoryTest.php index 67a72752..b97a2e95 100644 --- a/tests/Factory/Model/Revision/TransitionFactoryTest.php +++ b/tests/Factory/Model/Revision/TransitionFactoryTest.php @@ -20,6 +20,7 @@ protected function setUp(): void $this->data = [ 'label' => 'Transition 1', 'guard' => 'count > 1', + 'expression' => '{count: count + 1}', 'fromPlaces' => [1, 2], 'toPlaces' => [2, 3], 'commands' => [], @@ -31,6 +32,7 @@ public function testCreateFromArray(): void $transition = TransitionFactory::createFromArray($this->data); $this->assertSame('Transition 1', $transition->getLabel()); $this->assertSame('count > 1', $transition->getGuard()); + $this->assertSame('{count: count + 1}', $transition->getExpression()); $this->assertSame([1, 2], $transition->getFromPlaces()); $this->assertSame([2, 3], $transition->getToPlaces()); $this->assertIsArray($transition->getCommands()); From 3f5731df1b8229834bc73d15c2f220509eb5aac8 Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 2 May 2022 20:25:43 +0700 Subject: [PATCH 48/85] Revert refresh bug to store bug error message --- src/Repository/BugRepository.php | 2 -- tests/Repository/BugRepositoryTest.php | 1 - 2 files changed, 3 deletions(-) diff --git a/src/Repository/BugRepository.php b/src/Repository/BugRepository.php index fac7f7f6..bd1ae320 100644 --- a/src/Repository/BugRepository.php +++ b/src/Repository/BugRepository.php @@ -63,8 +63,6 @@ public function stopRecording(BugInterface $bug): void { // Recording bug may take long time. Reconnect to flush changes. $this->getEntityManager()->getConnection()->connect(); - // Refresh so we don't update other fields while recording. - $this->getEntityManager()->refresh($bug); $bug->getVideo()->setRecording(false); $this->getEntityManager()->flush(); } diff --git a/tests/Repository/BugRepositoryTest.php b/tests/Repository/BugRepositoryTest.php index 6be72ba0..9aca76e6 100644 --- a/tests/Repository/BugRepositoryTest.php +++ b/tests/Repository/BugRepositoryTest.php @@ -156,7 +156,6 @@ public function testStopRecordingBug(): void { $this->connection->expects($this->once())->method('connect'); $this->bug->getVideo()->setRecording(true); - $this->manager->expects($this->once())->method('refresh')->with($this->bug); $this->manager->expects($this->once())->method('flush'); $this->manager->expects($this->once())->method('getConnection')->willReturn($this->connection); $this->bugRepository->stopRecording($this->bug); From d8c2bc242aae5e8b147f7c8808f2294793b7f37c Mon Sep 17 00:00:00 2001 From: tienvx Date: Wed, 4 May 2022 10:27:24 +0700 Subject: [PATCH 49/85] Fix running commands in old places while reducing steps --- src/Service/Step/Runner/ReduceStepsRunner.php | 3 +++ tests/Service/Step/Runner/ReduceStepsRunnerTest.php | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/Service/Step/Runner/ReduceStepsRunner.php b/src/Service/Step/Runner/ReduceStepsRunner.php index c938f337..d949e511 100644 --- a/src/Service/Step/Runner/ReduceStepsRunner.php +++ b/src/Service/Step/Runner/ReduceStepsRunner.php @@ -50,6 +50,9 @@ protected function runStep(StepInterface $step, RevisionInterface $revision, Rem $marking = $this->markingHelper->getMarking($this->petrinet, $step->getPlaces(), $step->getColor()); $transition = $this->petrinet->getTransitionById($step->getTransition()); if ($this->transitionService->isEnabled($transition, $marking)) { + $this->transitionService->fire($transition, $marking); + $step->setPlaces($this->markingHelper->getPlaces($marking)); + return parent::runStep($step, $revision, $driver); } diff --git a/tests/Service/Step/Runner/ReduceStepsRunnerTest.php b/tests/Service/Step/Runner/ReduceStepsRunnerTest.php index 45b5fcb9..897d911c 100644 --- a/tests/Service/Step/Runner/ReduceStepsRunnerTest.php +++ b/tests/Service/Step/Runner/ReduceStepsRunnerTest.php @@ -106,6 +106,18 @@ protected function assertRunSteps( array_slice($this->steps, 0, count($steps) + $nextStepDisabled) )) ->willReturnOnConsecutiveCalls(...[...array_fill(0, count($steps), true), !$nextStepDisabled]); + $this->transitionService + ->expects($this->exactly(count($steps))) + ->method('fire') + ->withConsecutive(...array_map( + fn (StepInterface $step) => [$this->transitions[$step->getTransition()], $this->marking], + array_slice($this->steps, 0, count($steps)) + )); + $this->markingHelper + ->expects($this->exactly(count($steps))) + ->method('getPlaces') + ->with($this->marking) + ->willReturn([]); } else { $this->transitionService ->expects($this->exactly(count($this->steps))) From 48904e4e2fd291a2280eeba80ab80bc2fffcfbb3 Mon Sep 17 00:00:00 2001 From: tienvx Date: Wed, 4 May 2022 16:20:04 +0700 Subject: [PATCH 50/85] Revert code because step's color is separated from command values --- src/Reducer/HandlerTemplate.php | 14 +- src/Resources/config/services.php | 30 +--- src/Service/Bug/BugHelper.php | 12 +- src/Service/Step/Runner/BugStepsRunner.php | 2 +- src/Service/Step/Runner/RecordStepsRunner.php | 7 - src/Service/Step/Runner/ReduceStepsRunner.php | 61 -------- src/Service/Step/StepHelper.php | 39 ------ src/Service/Step/StepHelperInterface.php | 10 -- tests/Reducer/HandlerTestCase.php | 14 +- tests/Reducer/Random/RandomHandlerTest.php | 3 +- tests/Reducer/Split/SplitHandlerTest.php | 3 +- tests/Service/Bug/BugHelperTest.php | 15 +- ...sRunnerTest.php => BugStepsRunnerTest.php} | 16 ++- .../Step/Runner/BugStepsRunnerTestCase.php | 16 --- .../Step/Runner/ReduceStepsRunnerTest.php | 132 ------------------ tests/Service/Step/StepHelperTest.php | 70 ---------- 16 files changed, 35 insertions(+), 409 deletions(-) delete mode 100644 src/Service/Step/Runner/RecordStepsRunner.php delete mode 100644 src/Service/Step/Runner/ReduceStepsRunner.php delete mode 100644 src/Service/Step/StepHelper.php delete mode 100644 src/Service/Step/StepHelperInterface.php rename tests/Service/Step/Runner/{RecordStepsRunnerTest.php => BugStepsRunnerTest.php} (57%) delete mode 100644 tests/Service/Step/Runner/BugStepsRunnerTestCase.php delete mode 100644 tests/Service/Step/Runner/ReduceStepsRunnerTest.php delete mode 100644 tests/Service/Step/StepHelperTest.php diff --git a/src/Reducer/HandlerTemplate.php b/src/Reducer/HandlerTemplate.php index 8e939d06..01c88bdd 100644 --- a/src/Reducer/HandlerTemplate.php +++ b/src/Reducer/HandlerTemplate.php @@ -8,29 +8,25 @@ use Tienvx\Bundle\MbtBundle\Model\BugInterface; use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Builder\StepsBuilderInterface; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\ReduceStepsRunner; -use Tienvx\Bundle\MbtBundle\Service\Step\StepHelperInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; abstract class HandlerTemplate implements HandlerInterface { protected BugRepositoryInterface $bugRepository; protected MessageBusInterface $messageBus; - protected ReduceStepsRunner $stepsRunner; + protected BugStepsRunner $stepsRunner; protected StepsBuilderInterface $stepsBuilder; - protected StepHelperInterface $stepHelper; public function __construct( BugRepositoryInterface $bugRepository, MessageBusInterface $messageBus, - ReduceStepsRunner $stepsRunner, - StepsBuilderInterface $stepsBuilder, - StepHelperInterface $stepHelper + BugStepsRunner $stepsRunner, + StepsBuilderInterface $stepsBuilder ) { $this->bugRepository = $bugRepository; $this->messageBus = $messageBus; $this->stepsRunner = $stepsRunner; $this->stepsBuilder = $stepsBuilder; - $this->stepHelper = $stepHelper; } public function handle(BugInterface $bug, int $from, int $to): void @@ -42,7 +38,7 @@ public function handle(BugInterface $bug, int $from, int $to): void $bug->setDebug(false); $this->stepsRunner->run( - $this->stepHelper->cloneAndResetSteps($newSteps, $bug->getTask()->getModelRevision()), + $newSteps, $bug, function (Throwable $throwable) use ($bug, $newSteps): void { if ($throwable->getMessage() === $bug->getMessage()) { diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index cee10a39..a8a279cf 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -64,13 +64,10 @@ use Tienvx\Bundle\MbtBundle\Service\SelenoidHelperInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Builder\ShortestPathStepsBuilder; use Tienvx\Bundle\MbtBundle\Service\Step\Builder\StepsBuilderInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\ExploreStepsRunner; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\RecordStepsRunner; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\ReduceStepsRunner; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepRunner; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepRunnerInterface; -use Tienvx\Bundle\MbtBundle\Service\Step\StepHelper; -use Tienvx\Bundle\MbtBundle\Service\Step\StepHelperInterface; use Tienvx\Bundle\MbtBundle\Service\Task\TaskHelper; use Tienvx\Bundle\MbtBundle\Service\Task\TaskHelperInterface; use Tienvx\Bundle\MbtBundle\Validator\TagsValidator; @@ -137,9 +134,8 @@ ->args([ service(BugRepositoryInterface::class), service(MessageBusInterface::class), - service(ReduceStepsRunner::class), + service(BugStepsRunner::class), service(StepsBuilderInterface::class), - service(StepHelperInterface::class), ]) ->set(RandomReducer::class) ->args([ @@ -155,9 +151,8 @@ ->args([ service(BugRepositoryInterface::class), service(MessageBusInterface::class), - service(ReduceStepsRunner::class), + service(BugStepsRunner::class), service(StepsBuilderInterface::class), - service(StepHelperInterface::class), ]) ->set(SplitReducer::class) ->args([ @@ -235,8 +230,7 @@ service(BugRepositoryInterface::class), service(MessageBusInterface::class), service(BugNotifierInterface::class), - service(StepHelperInterface::class), - service(RecordStepsRunner::class), + service(BugStepsRunner::class), service(ConfigInterface::class), ]) ->alias(BugHelperInterface::class, BugHelper::class) @@ -264,12 +258,6 @@ ]) ->alias(PetrinetDomainLogicInterface::class, PetrinetDomainLogic::class) - ->set(StepHelper::class) - ->args([ - service(ModelHelperInterface::class), - ]) - ->alias(StepHelperInterface::class, StepHelper::class) - ->set(StepRunner::class) ->args([ service(CommandRunnerManagerInterface::class), @@ -282,15 +270,7 @@ service(StepRunnerInterface::class), service(ConfigInterface::class), ]) - ->set(ReduceStepsRunner::class) - ->args([ - service(SelenoidHelperInterface::class), - service(StepRunnerInterface::class), - service(PetrinetHelperInterface::class), - service(MarkingHelperInterface::class), - service(GuardedTransitionServiceInterface::class), - ]) - ->set(RecordStepsRunner::class) + ->set(BugStepsRunner::class) ->args([ service(SelenoidHelperInterface::class), service(StepRunnerInterface::class), diff --git a/src/Service/Bug/BugHelper.php b/src/Service/Bug/BugHelper.php index 665b7c2d..cbb4f976 100644 --- a/src/Service/Bug/BugHelper.php +++ b/src/Service/Bug/BugHelper.php @@ -13,8 +13,7 @@ use Tienvx\Bundle\MbtBundle\Reducer\ReducerManagerInterface; use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\RecordStepsRunner; -use Tienvx\Bundle\MbtBundle\Service\Step\StepHelperInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; class BugHelper implements BugHelperInterface { @@ -22,8 +21,7 @@ class BugHelper implements BugHelperInterface protected BugRepositoryInterface $bugRepository; protected MessageBusInterface $messageBus; protected BugNotifierInterface $bugNotifier; - protected StepHelperInterface $stepHelper; - protected RecordStepsRunner $stepsRunner; + protected BugStepsRunner $stepsRunner; protected ConfigInterface $config; public function __construct( @@ -31,15 +29,13 @@ public function __construct( BugRepositoryInterface $bugRepository, MessageBusInterface $messageBus, BugNotifierInterface $bugNotifier, - StepHelperInterface $stepHelper, - RecordStepsRunner $stepsRunner, + BugStepsRunner $stepsRunner, ConfigInterface $config ) { $this->reducerManager = $reducerManager; $this->bugRepository = $bugRepository; $this->messageBus = $messageBus; $this->bugNotifier = $bugNotifier; - $this->stepHelper = $stepHelper; $this->stepsRunner = $stepsRunner; $this->config = $config; } @@ -101,7 +97,7 @@ public function recordVideo(int $bugId): void $this->bugRepository->startRecording($bug); $bug->setDebug(true); $this->stepsRunner->run( - $this->stepHelper->cloneAndResetSteps($bug->getSteps(), $bug->getTask()->getModelRevision()), + $bug->getSteps(), $bug, function (Throwable $throwable) use ($bug) { $bug->getVideo()->setErrorMessage( diff --git a/src/Service/Step/Runner/BugStepsRunner.php b/src/Service/Step/Runner/BugStepsRunner.php index 9a4a0336..3635dabb 100644 --- a/src/Service/Step/Runner/BugStepsRunner.php +++ b/src/Service/Step/Runner/BugStepsRunner.php @@ -5,7 +5,7 @@ use Throwable; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; -abstract class BugStepsRunner extends StepsRunner +class BugStepsRunner extends StepsRunner { protected function catchException(callable $handleException, Throwable $throwable, ?StepInterface $step): void { diff --git a/src/Service/Step/Runner/RecordStepsRunner.php b/src/Service/Step/Runner/RecordStepsRunner.php deleted file mode 100644 index 2831906e..00000000 --- a/src/Service/Step/Runner/RecordStepsRunner.php +++ /dev/null @@ -1,7 +0,0 @@ -petrinetHelper = $petrinetHelper; - $this->markingHelper = $markingHelper; - $this->transitionService = $transitionService; - } - - protected function start(DebugInterface $entity): RemoteWebDriver - { - $this->petrinet = $this->petrinetHelper->build($entity->getTask()->getModelRevision()); - - return parent::start($entity); - } - - protected function stop(?RemoteWebDriver $driver): void - { - parent::stop($driver); - $this->petrinet = null; - } - - protected function runStep(StepInterface $step, RevisionInterface $revision, RemoteWebDriver $driver): bool - { - $marking = $this->markingHelper->getMarking($this->petrinet, $step->getPlaces(), $step->getColor()); - $transition = $this->petrinet->getTransitionById($step->getTransition()); - if ($this->transitionService->isEnabled($transition, $marking)) { - $this->transitionService->fire($transition, $marking); - $step->setPlaces($this->markingHelper->getPlaces($marking)); - - return parent::runStep($step, $revision, $driver); - } - - return false; - } -} diff --git a/src/Service/Step/StepHelper.php b/src/Service/Step/StepHelper.php deleted file mode 100644 index 6d16b9db..00000000 --- a/src/Service/Step/StepHelper.php +++ /dev/null @@ -1,39 +0,0 @@ -modelHelper = $modelHelper; - } - - public function cloneAndResetSteps(array $steps, RevisionInterface $revision): array - { - $lastColor = new Color(); - $lastPlaces = $this->modelHelper->getStartPlaceIds($revision); - $newSteps = []; - foreach ($steps as $step) { - if (!$step instanceof StepInterface) { - throw new UnexpectedValueException(sprintf('Step must be instance of "%s".', StepInterface::class)); - } - $newStep = clone $step; - $newStep->setColor($lastColor); - $newStep->setPlaces($lastPlaces); - $newSteps[] = $newStep; - $lastColor = clone $step->getColor(); - $lastPlaces = $step->getPlaces(); - } - - return $newSteps; - } -} diff --git a/src/Service/Step/StepHelperInterface.php b/src/Service/Step/StepHelperInterface.php deleted file mode 100644 index 5d2f2411..00000000 --- a/src/Service/Step/StepHelperInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -bugRepository = $this->createMock(BugRepositoryInterface::class); $this->messageBus = $this->createMock(MessageBusInterface::class); - $this->stepsRunner = $this->createMock(ReduceStepsRunner::class); + $this->stepsRunner = $this->createMock(BugStepsRunner::class); $this->stepsBuilder = $this->createMock(StepsBuilderInterface::class); - $this->stepHelper = $this->createMock(StepHelperInterface::class); $this->newSteps = [ $this->createMock(StepInterface::class), $this->createMock(StepInterface::class), @@ -83,11 +80,6 @@ public function testHandleOldBug(): void */ public function testHandle(?Throwable $exception, bool $updateSteps): void { - $this->stepHelper - ->expects($this->once()) - ->method('cloneAndResetSteps') - ->with($this->newSteps, $this->revision) - ->willReturnArgument(0); $this->stepsRunner->expects($this->once()) ->method('run') ->with( diff --git a/tests/Reducer/Random/RandomHandlerTest.php b/tests/Reducer/Random/RandomHandlerTest.php index acd7d3eb..3a1b866a 100644 --- a/tests/Reducer/Random/RandomHandlerTest.php +++ b/tests/Reducer/Random/RandomHandlerTest.php @@ -27,8 +27,7 @@ protected function setUp(): void $this->bugRepository, $this->messageBus, $this->stepsRunner, - $this->stepsBuilder, - $this->stepHelper + $this->stepsBuilder ); } } diff --git a/tests/Reducer/Split/SplitHandlerTest.php b/tests/Reducer/Split/SplitHandlerTest.php index 878bf5e2..63515131 100644 --- a/tests/Reducer/Split/SplitHandlerTest.php +++ b/tests/Reducer/Split/SplitHandlerTest.php @@ -27,8 +27,7 @@ protected function setUp(): void $this->bugRepository, $this->messageBus, $this->stepsRunner, - $this->stepsBuilder, - $this->stepHelper + $this->stepsBuilder ); } } diff --git a/tests/Service/Bug/BugHelperTest.php b/tests/Service/Bug/BugHelperTest.php index d119ed2c..ae205a27 100644 --- a/tests/Service/Bug/BugHelperTest.php +++ b/tests/Service/Bug/BugHelperTest.php @@ -25,8 +25,7 @@ use Tienvx\Bundle\MbtBundle\Service\Bug\BugHelperInterface; use Tienvx\Bundle\MbtBundle\Service\Bug\BugNotifierInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\RecordStepsRunner; -use Tienvx\Bundle\MbtBundle\Service\Step\StepHelperInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; /** * @covers \Tienvx\Bundle\MbtBundle\Service\Bug\BugHelper @@ -48,8 +47,7 @@ class BugHelperTest extends TestCase protected BugRepositoryInterface $bugRepository; protected MessageBusInterface $messageBus; protected BugNotifierInterface $bugNotifier; - protected StepHelperInterface $stepHelper; - protected RecordStepsRunner $stepsRunner; + protected BugStepsRunner $stepsRunner; protected ConfigInterface $config; protected BugHelperInterface $helper; protected Revision $revision; @@ -62,15 +60,13 @@ protected function setUp(): void $this->bugRepository = $this->createMock(BugRepositoryInterface::class); $this->messageBus = $this->createMock(MessageBusInterface::class); $this->bugNotifier = $this->createMock(BugNotifierInterface::class); - $this->stepHelper = $this->createMock(StepHelperInterface::class); - $this->stepsRunner = $this->createMock(RecordStepsRunner::class); + $this->stepsRunner = $this->createMock(BugStepsRunner::class); $this->config = $this->createMock(ConfigInterface::class); $this->helper = new BugHelper( $this->reducerManager, $this->bugRepository, $this->messageBus, $this->bugNotifier, - $this->stepHelper, $this->stepsRunner, $this->config ); @@ -265,11 +261,6 @@ public function testRecordVideo(?Throwable $exception, ?string $expectedVideoErr }) ); $this->bugRepository->expects($this->once())->method('find')->with(123)->willReturn($this->bug); - $this->stepHelper - ->expects($this->once()) - ->method('cloneAndResetSteps') - ->with($this->bug->getSteps(), $this->revision) - ->willReturnArgument(0); $this->bugRepository->expects($this->once())->method('startRecording')->with($this->bug); $this->bugRepository->expects($this->once())->method('stopRecording')->with($this->bug); $this->helper->recordVideo(123); diff --git a/tests/Service/Step/Runner/RecordStepsRunnerTest.php b/tests/Service/Step/Runner/BugStepsRunnerTest.php similarity index 57% rename from tests/Service/Step/Runner/RecordStepsRunnerTest.php rename to tests/Service/Step/Runner/BugStepsRunnerTest.php index cfbcd0e5..ee67a662 100644 --- a/tests/Service/Step/Runner/RecordStepsRunnerTest.php +++ b/tests/Service/Step/Runner/BugStepsRunnerTest.php @@ -2,10 +2,10 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service\Step\Runner; -use Tienvx\Bundle\MbtBundle\Service\Step\Runner\RecordStepsRunner; +use Exception; +use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; /** - * @covers \Tienvx\Bundle\MbtBundle\Service\Step\Runner\RecordStepsRunner * @covers \Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner * @covers \Tienvx\Bundle\MbtBundle\Service\Step\Runner\StepsRunner * @@ -17,11 +17,19 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Bug * @uses \Tienvx\Bundle\MbtBundle\Model\Bug\Step */ -class RecordStepsRunnerTest extends BugStepsRunnerTestCase +class BugStepsRunnerTest extends StepsRunnerTestCase { protected function setUp(): void { parent::setUp(); - $this->stepsRunner = new RecordStepsRunner($this->selenoidHelper, $this->stepRunner); + $this->stepsRunner = new BugStepsRunner($this->selenoidHelper, $this->stepRunner); + } + + protected function assertHandlingException(Exception $exception, array $bugSteps = []): void + { + $this->handleException + ->expects($this->once()) + ->method('__invoke') + ->with($exception); } } diff --git a/tests/Service/Step/Runner/BugStepsRunnerTestCase.php b/tests/Service/Step/Runner/BugStepsRunnerTestCase.php deleted file mode 100644 index cc1b4774..00000000 --- a/tests/Service/Step/Runner/BugStepsRunnerTestCase.php +++ /dev/null @@ -1,16 +0,0 @@ -handleException - ->expects($this->once()) - ->method('__invoke') - ->with($exception); - } -} diff --git a/tests/Service/Step/Runner/ReduceStepsRunnerTest.php b/tests/Service/Step/Runner/ReduceStepsRunnerTest.php deleted file mode 100644 index 897d911c..00000000 --- a/tests/Service/Step/Runner/ReduceStepsRunnerTest.php +++ /dev/null @@ -1,132 +0,0 @@ -petrinetHelper = $this->createMock(PetrinetHelperInterface::class); - $this->markingHelper = $this->createMock(MarkingHelperInterface::class); - $this->transitionService = $this->createMock(GuardedTransitionServiceInterface::class); - $this->stepsRunner = new ReduceStepsRunner( - $this->selenoidHelper, - $this->stepRunner, - $this->petrinetHelper, - $this->markingHelper, - $this->transitionService - ); - $this->petrinet = $this->createMock(PetrinetInterface::class); - $this->petrinetHelper - ->expects($this->once()) - ->method('build') - ->with($this->revision) - ->willReturn($this->petrinet); - $this->marking = $this->createMock(ColorfulMarkingInterface::class); - $this->transitions = [ - 0 => $this->createMock(GuardedTransitionInterface::class), - 1 => $this->createMock(GuardedTransitionInterface::class), - 2 => $this->createMock(GuardedTransitionInterface::class), - 3 => $this->createMock(GuardedTransitionInterface::class), - ]; - } - - /** - * @dataProvider entityProvider - */ - public function testCanNotRunThirdStep(DebugInterface $entity): void - { - $this->selenoidHelper - ->expects($this->once()) - ->method('createDriver') - ->with($entity) - ->willReturn($this->driver); - $this->driver->expects($this->once())->method('quit'); - $this->assertRunSteps(array_slice($this->steps, 0, 2), null, true); - $this->handleException->expects($this->never())->method('__invoke'); - $this->stepsRunner->run($this->steps, $entity, $this->handleException); - } - - protected function assertRunSteps( - array $steps = [], - ?Exception $exception = null, - bool $nextStepDisabled = false - ): void { - parent::assertRunSteps($steps, $exception); - $this->markingHelper - ->expects($this->exactly(count($steps) + $nextStepDisabled)) - ->method('getMarking') - ->withConsecutive(...array_map( - fn (StepInterface $step) => [$this->petrinet, $step->getPlaces(), $step->getColor()], - array_slice($this->steps, 0, count($steps) + $nextStepDisabled) - )) - ->willReturn($this->marking); - $this->petrinet - ->expects($this->exactly(count($steps) + $nextStepDisabled)) - ->method('getTransitionById') - ->willReturnCallback(fn (int $id) => $this->transitions[$id]); - if (count($steps) < count($this->steps)) { - $this->transitionService - ->expects($this->exactly(count($steps) + $nextStepDisabled)) - ->method('isEnabled') - ->withConsecutive(...array_map( - fn (StepInterface $step) => [$this->transitions[$step->getTransition()], $this->marking], - array_slice($this->steps, 0, count($steps) + $nextStepDisabled) - )) - ->willReturnOnConsecutiveCalls(...[...array_fill(0, count($steps), true), !$nextStepDisabled]); - $this->transitionService - ->expects($this->exactly(count($steps))) - ->method('fire') - ->withConsecutive(...array_map( - fn (StepInterface $step) => [$this->transitions[$step->getTransition()], $this->marking], - array_slice($this->steps, 0, count($steps)) - )); - $this->markingHelper - ->expects($this->exactly(count($steps))) - ->method('getPlaces') - ->with($this->marking) - ->willReturn([]); - } else { - $this->transitionService - ->expects($this->exactly(count($this->steps))) - ->method('isEnabled') - ->withConsecutive(...array_map( - fn (StepInterface $step) => [$this->transitions[$step->getTransition()], $this->marking], - $this->steps - )) - ->willReturn(true); - } - } -} diff --git a/tests/Service/Step/StepHelperTest.php b/tests/Service/Step/StepHelperTest.php deleted file mode 100644 index 83f7353c..00000000 --- a/tests/Service/Step/StepHelperTest.php +++ /dev/null @@ -1,70 +0,0 @@ -modelHelper = $this->createMock(ModelHelperInterface::class); - $this->stepHelper = new StepHelper($this->modelHelper); - $this->revision = $this->createMock(RevisionInterface::class); - $this->modelHelper - ->expects($this->once()) - ->method('getStartPlaceIds') - ->with($this->revision) - ->willReturn($this->startPlaces); - } - - public function testCloneInvalidSteps(): void - { - $this->expectExceptionObject( - new UnexpectedValueException(sprintf('Step must be instance of "%s".', StepInterface::class)) - ); - $this->stepHelper->cloneAndResetSteps([new \stdClass()], $this->revision); - } - - public function testCloneStepsAndResetColor(): void - { - $steps = [ - new Step([0], new Color(), 0), - new Step([1], new Color(), 1), - new Step([2], new Color(), 2), - new Step([3], new Color(), 3), - ]; - $newSteps = $this->stepHelper->cloneAndResetSteps($steps, $this->revision); - $this->assertCount(count($steps), $newSteps); - foreach ($newSteps as $index => $newStep) { - $this->assertInstanceOf(StepInterface::class, $newStep); - $this->assertSame($steps[$index]->getTransition(), $newStep->getTransition()); - $this->assertNotSame($steps[$index]->getColor(), $newStep->getColor()); - if ($index > 0) { - $this->assertNotSame($steps[$index - 1]->getColor(), $newStep->getColor()); - $this->assertSame($steps[$index - 1]->getColor()->getValues(), $newStep->getColor()->getValues()); - $this->assertSame($steps[$index - 1]->getPlaces(), $newStep->getPlaces()); - } else { - $this->assertSame([], $newStep->getColor()->getValues()); - $this->assertSame($this->startPlaces, $newStep->getPlaces()); - } - } - } -} From 6088b492ee3bead6961aa802401dd3f14d47d9f6 Mon Sep 17 00:00:00 2001 From: tienvx Date: Wed, 4 May 2022 23:48:17 +0700 Subject: [PATCH 51/85] Shortest path without color --- src/Model/Bug/Step.php | 12 +- src/Reducer/DispatcherTemplate.php | 6 +- src/Reducer/Random/RandomDispatcher.php | 12 +- src/Reducer/Split/SplitDispatcher.php | 9 +- src/Resources/config/services.php | 10 +- .../AStar/PetrinetDomainLogicInterface.php | 11 -- .../Builder}/PetrinetDomainLogic.php | 22 ++- .../Step/Builder/ShortestPathStepsBuilder.php | 58 ++++++-- tests/Model/Bug/StepTest.php | 11 +- tests/Reducer/DispatcherTestCase.php | 1 - tests/Reducer/Random/RandomDispatcherTest.php | 4 +- tests/Reducer/Split/SplitDispatcherTest.php | 12 +- .../Builder}/PetrinetDomainLogicTest.php | 30 ++-- .../Builder/ShortestPathStepsBuilderTest.php | 129 ++++++++++-------- 14 files changed, 152 insertions(+), 175 deletions(-) delete mode 100644 src/Service/AStar/PetrinetDomainLogicInterface.php rename src/Service/{AStar => Step/Builder}/PetrinetDomainLogic.php (80%) rename tests/Service/{AStar => Step/Builder}/PetrinetDomainLogicTest.php (83%) diff --git a/src/Model/Bug/Step.php b/src/Model/Bug/Step.php index ea95112f..4322cb29 100644 --- a/src/Model/Bug/Step.php +++ b/src/Model/Bug/Step.php @@ -42,15 +42,9 @@ public function __clone() public function getUniqueNodeId(): string { - $places = $this->places; - ksort($places); - $colorValues = $this->color->getValues(); - ksort($colorValues); - - return md5(serialize([ - 'places' => $places, - 'color' => $colorValues, - ])); + ksort($this->places); + + return md5(serialize($this->places)); } public function setColor(ColorInterface $color): void diff --git a/src/Reducer/DispatcherTemplate.php b/src/Reducer/DispatcherTemplate.php index 7c5c7459..39e43460 100644 --- a/src/Reducer/DispatcherTemplate.php +++ b/src/Reducer/DispatcherTemplate.php @@ -8,7 +8,7 @@ abstract class DispatcherTemplate implements DispatcherInterface { - protected const MIN_PAIR_LENGTH = 2; // 3 steps + protected const MIN_STEPS = 3; protected MessageBusInterface $messageBus; @@ -21,7 +21,7 @@ public function dispatch(BugInterface $bug): int { $steps = $bug->getSteps(); - if (count($steps) < $this->minSteps()) { + if (count($steps) < self::MIN_STEPS) { return 0; } @@ -41,6 +41,4 @@ protected function maxPairs(array $steps): int { return ceil(sqrt(count($steps))); } - - abstract protected function minSteps(): int; } diff --git a/src/Reducer/Random/RandomDispatcher.php b/src/Reducer/Random/RandomDispatcher.php index 995bb262..c7ed50a9 100644 --- a/src/Reducer/Random/RandomDispatcher.php +++ b/src/Reducer/Random/RandomDispatcher.php @@ -14,21 +14,11 @@ protected function getPairs(array $steps): array while (count($pairs) < $maxPairs) { $pair = array_rand(range(0, $length - 1), 2); - if ($pair[1] - $pair[0] >= static::MIN_PAIR_LENGTH && !in_array($pair, $pairs)) { + if (!in_array($pair, $pairs)) { $pairs[] = $pair; } } return $pairs; } - - protected function minSteps(): int - { - return 3; - } - - protected function maxPairs(array $steps): int - { - return count($steps) <= 3 ? 1 : parent::maxPairs($steps); - } } diff --git a/src/Reducer/Split/SplitDispatcher.php b/src/Reducer/Split/SplitDispatcher.php index ebfa9e11..8729e0b3 100644 --- a/src/Reducer/Split/SplitDispatcher.php +++ b/src/Reducer/Split/SplitDispatcher.php @@ -17,16 +17,9 @@ protected function getPairs(array $steps): array $range[] = $length - 1; } for ($i = 0; $i < count($range) - 1; ++$i) { - if ($range[$i + 1] - $range[$i] >= static::MIN_PAIR_LENGTH) { - $pairs[] = [$range[$i], $range[$i + 1]]; - } + $pairs[] = [$range[$i], $range[$i + 1]]; } return $pairs; } - - protected function minSteps(): int - { - return 5; - } } diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index a8a279cf..bafa7aed 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -45,8 +45,6 @@ use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; use Tienvx\Bundle\MbtBundle\Repository\TaskRepository; use Tienvx\Bundle\MbtBundle\Repository\TaskRepositoryInterface; -use Tienvx\Bundle\MbtBundle\Service\AStar\PetrinetDomainLogic; -use Tienvx\Bundle\MbtBundle\Service\AStar\PetrinetDomainLogicInterface; use Tienvx\Bundle\MbtBundle\Service\Bug\BugHelper; use Tienvx\Bundle\MbtBundle\Service\Bug\BugHelperInterface; use Tienvx\Bundle\MbtBundle\Service\Bug\BugNotifierInterface; @@ -247,16 +245,10 @@ ->set(ShortestPathStepsBuilder::class) ->args([ service(PetrinetHelperInterface::class), - service(PetrinetDomainLogicInterface::class), - ]) - ->alias(StepsBuilderInterface::class, ShortestPathStepsBuilder::class) - - ->set(PetrinetDomainLogic::class) - ->args([ service(GuardedTransitionServiceInterface::class), service(MarkingHelperInterface::class), ]) - ->alias(PetrinetDomainLogicInterface::class, PetrinetDomainLogic::class) + ->alias(StepsBuilderInterface::class, ShortestPathStepsBuilder::class) ->set(StepRunner::class) ->args([ diff --git a/src/Service/AStar/PetrinetDomainLogicInterface.php b/src/Service/AStar/PetrinetDomainLogicInterface.php deleted file mode 100644 index 1df6aecc..00000000 --- a/src/Service/AStar/PetrinetDomainLogicInterface.php +++ /dev/null @@ -1,11 +0,0 @@ -transitionService = $transitionService; $this->markingHelper = $markingHelper; - } - - public function setPetrinet(?PetrinetInterface $petrinet): void - { $this->petrinet = $petrinet; } @@ -46,8 +44,8 @@ public function calculateEstimatedCost(mixed $fromNode, mixed $toNode): float|in $tokensDiff += abs($toNode->getPlaces()[$place] - $fromNode->getPlaces()[$place]); } } - // Estimate it will took N transitions to move N tokens if color is the same, twice if color is not the same. - return $tokensDiff * (($fromNode->getColor()->getValues() != $toNode->getColor()->getValues()) + 1); + // Estimate it will took N transitions to move N tokens. + return $tokensDiff; } public function calculateRealCost(mixed $node, mixed $adjacent): float|int @@ -62,10 +60,6 @@ public function getAdjacentNodes(mixed $node): iterable throw new RuntimeException('The provided node is invalid'); } - if (!$this->petrinet instanceof PetrinetInterface) { - throw new RuntimeException('Petrinet is required'); - } - $adjacents = []; $marking = $this->markingHelper->getMarking($this->petrinet, $node->getPlaces(), $node->getColor()); foreach ($this->transitionService->getEnabledTransitions($this->petrinet, $marking) as $transition) { diff --git a/src/Service/Step/Builder/ShortestPathStepsBuilder.php b/src/Service/Step/Builder/ShortestPathStepsBuilder.php index 738a602c..c12bcade 100644 --- a/src/Service/Step/Builder/ShortestPathStepsBuilder.php +++ b/src/Service/Step/Builder/ShortestPathStepsBuilder.php @@ -4,24 +4,31 @@ use Generator; use JMGQ\AStar\AStar; +use RuntimeException; +use SingleColorPetrinet\Model\PetrinetInterface; +use SingleColorPetrinet\Service\GuardedTransitionServiceInterface; use Tienvx\Bundle\MbtBundle\Exception\ExceptionInterface; use Tienvx\Bundle\MbtBundle\Exception\OutOfRangeException; +use Tienvx\Bundle\MbtBundle\Model\Bug\Step; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; use Tienvx\Bundle\MbtBundle\Model\BugInterface; -use Tienvx\Bundle\MbtBundle\Service\AStar\PetrinetDomainLogicInterface; +use Tienvx\Bundle\MbtBundle\Service\Petrinet\MarkingHelperInterface; use Tienvx\Bundle\MbtBundle\Service\Petrinet\PetrinetHelperInterface; class ShortestPathStepsBuilder implements StepsBuilderInterface { protected PetrinetHelperInterface $petrinetHelper; - protected PetrinetDomainLogicInterface $petrinetDomainLogic; + protected GuardedTransitionServiceInterface $transitionService; + protected MarkingHelperInterface $markingHelper; public function __construct( PetrinetHelperInterface $petrinetHelper, - PetrinetDomainLogicInterface $petrinetDomainLogic + GuardedTransitionServiceInterface $transitionService, + MarkingHelperInterface $markingHelper ) { $this->petrinetHelper = $petrinetHelper; - $this->petrinetDomainLogic = $petrinetDomainLogic; + $this->transitionService = $transitionService; + $this->markingHelper = $markingHelper; } /** @@ -30,23 +37,46 @@ public function __construct( public function create(BugInterface $bug, int $from, int $to): Generator { yield from array_slice($bug->getSteps(), 0, $from); - yield from $this->getSteps($bug, $from, $to); - yield from array_slice($bug->getSteps(), $to + 1); + $petrinet = $this->petrinetHelper->build($bug->getTask()->getModelRevision()); + $shortestSteps = $this->getShortestSteps($bug->getSteps(), $from, $to, $petrinet); + $lastStep = end($shortestSteps); + reset($shortestSteps); + yield from $shortestSteps; + yield from $this->getRemainingSteps(array_slice($bug->getSteps(), $to + 1), $lastStep, $petrinet); } - protected function getSteps(BugInterface $bug, int $from, int $to): iterable + protected function getShortestSteps(array $steps, int $from, int $to, PetrinetInterface $petrinet): iterable { - $fromStep = $bug->getSteps()[$from] ?? null; - $toStep = $bug->getSteps()[$to] ?? null; + $fromStep = $steps[$from] ?? null; + $toStep = $steps[$to] ?? null; if (!$fromStep instanceof StepInterface || !$toStep instanceof StepInterface) { - throw new OutOfRangeException('Can not create new steps using invalid range'); + throw new OutOfRangeException('Can not create shortest steps between invalid range'); } - $this->petrinetDomainLogic->setPetrinet($this->petrinetHelper->build($bug->getTask()->getModelRevision())); - - yield from (new AStar($this->petrinetDomainLogic))->run($fromStep, $toStep); + return (new AStar(new PetrinetDomainLogic($this->transitionService, $this->markingHelper, $petrinet)))->run( + $fromStep, + $toStep + ); + } - $this->petrinetDomainLogic->setPetrinet(null); + protected function getRemainingSteps(array $steps, StepInterface $lastStep, PetrinetInterface $petrinet): iterable + { + $marking = $this->markingHelper->getMarking($petrinet, $lastStep->getPlaces(), $lastStep->getColor()); + foreach ($steps as $step) { + if (!$step instanceof StepInterface) { + throw new OutOfRangeException('Remaining steps contains invalid step'); + } + $transition = $petrinet->getTransitionById($step->getTransition()); + if (!$this->transitionService->isEnabled($transition, $marking)) { + throw new RuntimeException('Can not connect remaining steps'); + } + $this->transitionService->fire($transition, $marking); + yield new Step( + $this->markingHelper->getPlaces($marking), + $marking->getColor(), + $step->getTransition() + ); + } } } diff --git a/tests/Model/Bug/StepTest.php b/tests/Model/Bug/StepTest.php index d9fb84c8..0e0b7a70 100644 --- a/tests/Model/Bug/StepTest.php +++ b/tests/Model/Bug/StepTest.php @@ -55,24 +55,19 @@ public function testClone(): void /** * @dataProvider nodeIdProvider */ - public function testGetUniqueNodeId(?array $places, ?ColorInterface $color, string $id): void + public function testGetUniqueNodeId(?array $places, string $id): void { if ($places) { $this->step->setPlaces($places); } - if ($color) { - $this->step->setColor($color); - } $this->assertSame($id, $this->step->getUniqueNodeId()); } public function nodeIdProvider(): array { return [ - [null, null, 'f179bfa0d0b5b6751e353f049461eda8'], - [null, new Color(['key1' => 'value1']), 'e13d72c92c38781375d3a400df07d43a'], - [[0 => 2, 1 => 1], null, 'e1b90c9311d5bd1d7fc90fd43d9bd49f'], - [[0 => 1, 1 => 1], new Color(['key2' => 'value2']), '61a579e02eb3ae787ef03ad40feb9a7d'], + [null, '89fefb193877ee62e29d1da5975dcc47'], + [[0 => 2, 1 => 1], '02878487ecf2302bf7ba2cc919514889'], ]; } diff --git a/tests/Reducer/DispatcherTestCase.php b/tests/Reducer/DispatcherTestCase.php index 075db5c5..9629eefe 100644 --- a/tests/Reducer/DispatcherTestCase.php +++ b/tests/Reducer/DispatcherTestCase.php @@ -56,7 +56,6 @@ protected function assertMessage(int $length): Callback } return $message->getBugId() === $this->bug->getId() && - $message->getFrom() + 2 <= $message->getTo() && $length === $message->getLength(); }); } diff --git a/tests/Reducer/Random/RandomDispatcherTest.php b/tests/Reducer/Random/RandomDispatcherTest.php index d5f83ab7..d8e7cb48 100644 --- a/tests/Reducer/Random/RandomDispatcherTest.php +++ b/tests/Reducer/Random/RandomDispatcherTest.php @@ -28,9 +28,7 @@ public function stepsProvider(): array [0, []], [1, []], [2, []], - [3, [ - [0, 2], - ]], + [3, range(1, 2)], [4, range(1, 2)], [5, range(1, 3)], [6, range(1, 3)], diff --git a/tests/Reducer/Split/SplitDispatcherTest.php b/tests/Reducer/Split/SplitDispatcherTest.php index 54ff22b9..daa0a1fd 100644 --- a/tests/Reducer/Split/SplitDispatcherTest.php +++ b/tests/Reducer/Split/SplitDispatcherTest.php @@ -28,8 +28,13 @@ public function stepsProvider(): array [0, []], [1, []], [2, []], - [3, []], - [4, []], + [3, [ + [0, 2], + ]], + [4, [ + [0, 2], + [2, 3], + ]], [5, [ [0, 2], [2, 4], @@ -37,6 +42,7 @@ public function stepsProvider(): array [6, [ [0, 2], [2, 4], + [4, 5], ]], [7, [ [0, 3], @@ -45,6 +51,7 @@ public function stepsProvider(): array [8, [ [0, 3], [3, 6], + [6, 7], ]], [9, [ [0, 3], @@ -60,6 +67,7 @@ public function stepsProvider(): array [0, 3], [3, 6], [6, 9], + [9, 10], ]], [12, [ [0, 3], diff --git a/tests/Service/AStar/PetrinetDomainLogicTest.php b/tests/Service/Step/Builder/PetrinetDomainLogicTest.php similarity index 83% rename from tests/Service/AStar/PetrinetDomainLogicTest.php rename to tests/Service/Step/Builder/PetrinetDomainLogicTest.php index 509d1a28..08593ab4 100644 --- a/tests/Service/AStar/PetrinetDomainLogicTest.php +++ b/tests/Service/Step/Builder/PetrinetDomainLogicTest.php @@ -1,6 +1,6 @@ transitionService = $this->createMock(GuardedTransitionServiceInterface::class); $this->markingHelper = $this->createMock(MarkingHelperInterface::class); - $this->petrinetDomainLogic = new PetrinetDomainLogic($this->transitionService, $this->markingHelper); $this->petrinet = $this->createMock(PetrinetInterface::class); + $this->petrinetDomainLogic = new PetrinetDomainLogic( + $this->transitionService, + $this->markingHelper, + $this->petrinet + ); $this->transitions = [ $transition1 = new GuardedTransition(), $transition2 = new GuardedTransition(), @@ -98,19 +101,11 @@ public function testCalculateEstimatedCost(Step $fromNode, Step $toNode, int $co public function estimatedCostProvider(): array { $color = new Color(['key' => 'value']); - $differentColor = new Color(['different key' => 'different value']); return [ [$this->getStep([0 => 1, 1 => 5, 2 => 3], $color), $this->getStep([], $color), 9], [$this->getStep([], $color), $this->getStep([0 => 3, 1 => 2, 2 => 2, 3 => 1], $color), 8], [$this->getStep([0 => 2, 1 => 4], $color), $this->getStep([1 => 5, 2 => 3, 3 => 1], $color), 7], - [$this->getStep([0 => 4, 1 => 1, 2 => 2], $color), $this->getStep([2 => 5], $differentColor), 16], - [$this->getStep([], $color), $this->getStep([0 => 4, 1 => 1], $differentColor), 10], - [ - $this->getStep([0 => 3, 1 => 2, 2 => 2], $color), - $this->getStep([1 => 7, 2 => 2, 3 => 3], $differentColor), - 22, - ], ]; } @@ -125,16 +120,9 @@ public function testGetAdjacentNodesOfInvalidNode(): void $this->petrinetDomainLogic->getAdjacentNodes('invalid'); } - public function testGetAdjacentNodesWithoutPetrinet(): void - { - $this->expectExceptionObject(new RuntimeException('Petrinet is required')); - $this->petrinetDomainLogic->getAdjacentNodes($this->getStep([], new Color())); - } - public function testGetAdjacentNodes(): void { $node = $this->getStep([12 => 34], new Color(['key' => 'value'])); - $this->petrinetDomainLogic->setPetrinet($this->petrinet); $this->markingHelper ->expects($this->exactly(count($this->transitions) + 1)) ->method('getMarking') diff --git a/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php b/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php index 7ffbf068..e407be5f 100644 --- a/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php +++ b/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php @@ -7,6 +7,7 @@ use Petrinet\Model\TransitionInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use RuntimeException; use SingleColorPetrinet\Builder\SingleColorPetrinetBuilder; use SingleColorPetrinet\Model\Color; use SingleColorPetrinet\Model\ColorfulFactory; @@ -20,10 +21,10 @@ use Tienvx\Bundle\MbtBundle\Entity\Task; use Tienvx\Bundle\MbtBundle\Exception\OutOfRangeException; use Tienvx\Bundle\MbtBundle\Model\Bug\Step; -use Tienvx\Bundle\MbtBundle\Service\AStar\PetrinetDomainLogic; use Tienvx\Bundle\MbtBundle\Service\Petrinet\MarkingHelper; use Tienvx\Bundle\MbtBundle\Service\Petrinet\MarkingHelperInterface; use Tienvx\Bundle\MbtBundle\Service\Petrinet\PetrinetHelperInterface; +use Tienvx\Bundle\MbtBundle\Service\Step\Builder\PetrinetDomainLogic; use Tienvx\Bundle\MbtBundle\Service\Step\Builder\ShortestPathStepsBuilder; /** @@ -34,7 +35,7 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Bug * @uses \Tienvx\Bundle\MbtBundle\Model\Bug\Step * @uses \Tienvx\Bundle\MbtBundle\Model\Task - * @uses \Tienvx\Bundle\MbtBundle\Service\AStar\PetrinetDomainLogic + * @uses \Tienvx\Bundle\MbtBundle\Service\Step\Builder\PetrinetDomainLogic * @uses \Tienvx\Bundle\MbtBundle\Service\ExpressionLanguage * @uses \Tienvx\Bundle\MbtBundle\Service\Petrinet\MarkingHelper */ @@ -67,6 +68,7 @@ class ShortestPathStepsBuilderTest extends TestCase protected TransitionInterface $chooseShipping; protected TransitionInterface $choosePayment; protected TransitionInterface $confirmOrder; + protected array $fromEmptyCartToConfirmOrderNodes; protected function setUp(): void { @@ -77,9 +79,9 @@ protected function setUp(): void $this->initPlaces($builder); $this->initTransitions($builder); $this->initPetrinet($builder); - $this->initPetrinetDomainLogic(); $this->initBug(); $this->initStepsBuilder(); + $this->initResults(); } protected function initPlaces(SingleColorPetrinetBuilder $builder): void @@ -161,12 +163,6 @@ protected function initPetrinet(SingleColorPetrinetBuilder $builder): void ->getPetrinet(); } - protected function initPetrinetDomainLogic(): void - { - $this->petrinetDomainLogic = new PetrinetDomainLogic($this->transitionService, $this->markingHelper); - $this->petrinetDomainLogic->setPetrinet($this->petrinet); - } - protected function initBug(): void { $this->bug = new Bug(); @@ -203,44 +199,16 @@ protected function geColor(int $products): Color protected function initStepsBuilder(): void { $this->petrinetHelper = $this->createMock(PetrinetHelperInterface::class); - $this->stepsBuilder = new ShortestPathStepsBuilder($this->petrinetHelper, $this->petrinetDomainLogic); - } - - protected function expectsPetrinetHelper(): void - { - $this->petrinetHelper - ->expects($this->once()) - ->method('build') - ->with($this->revision) - ->willReturn($this->petrinet); - } - - /** - * @dataProvider invalidRangeProvider - */ - public function testGetInvalidRange(int $from, int $to): void - { - $this->expectExceptionObject(new OutOfRangeException('Can not create new steps using invalid range')); - iterator_to_array($this->stepsBuilder->create($this->bug, $from, $to)); + $this->stepsBuilder = new ShortestPathStepsBuilder( + $this->petrinetHelper, + $this->transitionService, + $this->markingHelper + ); } - public function invalidRangeProvider(): array + protected function initResults(): void { - $validMinFrom = 0; - $validMaxTo = 16; - - return [ - [-1, $validMaxTo], - [$validMinFrom, 17], - [-1, 17], - ]; - } - - public function testGetShortestPathFromCartEmptyToCheckout(): void - { - $this->expectsPetrinetHelper(); - $nodes = $this->stepsBuilder->create($this->bug, 0, 12); - $this->assertNodes([ + $this->fromEmptyCartToConfirmOrderNodes = [ [ 'transition' => $this->clearCart->getId(), 'places' => [$this->cartEmpty->getId() => 1], @@ -276,7 +244,58 @@ public function testGetShortestPathFromCartEmptyToCheckout(): void 'places' => [$this->order->getId() => 1], 'color' => ['products' => 1], ], - ], $nodes); + ]; + } + + protected function expectsPetrinetHelper(): void + { + $this->petrinetHelper + ->expects($this->once()) + ->method('build') + ->with($this->revision) + ->willReturn($this->petrinet); + } + + /** + * @dataProvider invalidRangeProvider + */ + public function testGetInvalidRange(int $from, int $to): void + { + $this->expectExceptionObject(new OutOfRangeException('Can not create shortest steps between invalid range')); + iterator_to_array($this->stepsBuilder->create($this->bug, $from, $to)); + } + + public function invalidRangeProvider(): array + { + $validMinFrom = 0; + $validMaxTo = 16; + + return [ + [-1, $validMaxTo], + [$validMinFrom, 17], + [-1, 17], + ]; + } + + public function testGetShortestPathFromCartEmptyToCheckout(): void + { + $this->expectsPetrinetHelper(); + $nodes = $this->stepsBuilder->create($this->bug, 0, 12); + $this->assertNodes($this->fromEmptyCartToConfirmOrderNodes, $nodes); + } + + public function testGetShortestPathBetweenSameSteps(): void + { + $this->expectsPetrinetHelper(); + $nodes = $this->stepsBuilder->create($this->bug, 0, 10); + $this->assertNodes($this->fromEmptyCartToConfirmOrderNodes, $nodes); + } + + public function testGetShortestPathFromCartEmptyToFourProductsInCart(): void + { + $this->expectsPetrinetHelper(); + $this->expectExceptionObject(new RuntimeException('Can not connect remaining steps')); + iterator_to_array($this->stepsBuilder->create($this->bug, 0, 6)); } public function testGetShortestPathFromCartHasProductsToShipping(): void @@ -309,40 +328,30 @@ public function testGetShortestPathFromCartHasProductsToShipping(): void 'places' => [$this->cartHasProducts->getId() => 1], 'color' => ['products' => 4], ], - [ - 'transition' => $this->clearCart->getId(), - 'places' => [$this->cartEmpty->getId() => 1], - 'color' => ['products' => 0], - ], - [ - 'transition' => $this->addFirstProduct->getId(), - 'places' => [$this->cartHasProducts->getId() => 1], - 'color' => ['products' => 1], - ], [ 'transition' => $this->goToCheckout->getId(), 'places' => [$this->checkout->getId() => 1], - 'color' => ['products' => 1], + 'color' => ['products' => 4], ], [ 'transition' => $this->fillAddress->getId(), 'places' => [$this->address->getId() => 1], - 'color' => ['products' => 1], + 'color' => ['products' => 4], ], [ 'transition' => $this->chooseShipping->getId(), 'places' => [$this->shipping->getId() => 1], - 'color' => ['products' => 1], + 'color' => ['products' => 4], ], [ 'transition' => $this->choosePayment->getId(), 'places' => [$this->payment->getId() => 1], - 'color' => ['products' => 1], + 'color' => ['products' => 4], ], [ 'transition' => $this->confirmOrder->getId(), 'places' => [$this->order->getId() => 1], - 'color' => ['products' => 1], + 'color' => ['products' => 4], ], ], $nodes); } From d5158e8fdce4222838553968c182499666f3ae28 Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 6 May 2022 07:10:44 +0700 Subject: [PATCH 52/85] Don't show error can't connect steps while reducing it --- src/Service/Step/Builder/ShortestPathStepsBuilder.php | 4 ++-- tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Service/Step/Builder/ShortestPathStepsBuilder.php b/src/Service/Step/Builder/ShortestPathStepsBuilder.php index c12bcade..038c7c05 100644 --- a/src/Service/Step/Builder/ShortestPathStepsBuilder.php +++ b/src/Service/Step/Builder/ShortestPathStepsBuilder.php @@ -4,9 +4,9 @@ use Generator; use JMGQ\AStar\AStar; -use RuntimeException; use SingleColorPetrinet\Model\PetrinetInterface; use SingleColorPetrinet\Service\GuardedTransitionServiceInterface; +use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Tienvx\Bundle\MbtBundle\Exception\ExceptionInterface; use Tienvx\Bundle\MbtBundle\Exception\OutOfRangeException; use Tienvx\Bundle\MbtBundle\Model\Bug\Step; @@ -69,7 +69,7 @@ protected function getRemainingSteps(array $steps, StepInterface $lastStep, Petr } $transition = $petrinet->getTransitionById($step->getTransition()); if (!$this->transitionService->isEnabled($transition, $marking)) { - throw new RuntimeException('Can not connect remaining steps'); + throw new UnrecoverableMessageHandlingException('Can not connect remaining steps'); } $this->transitionService->fire($transition, $marking); yield new Step( diff --git a/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php b/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php index e407be5f..5898a5cd 100644 --- a/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php +++ b/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php @@ -7,7 +7,6 @@ use Petrinet\Model\TransitionInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use RuntimeException; use SingleColorPetrinet\Builder\SingleColorPetrinetBuilder; use SingleColorPetrinet\Model\Color; use SingleColorPetrinet\Model\ColorfulFactory; @@ -16,6 +15,7 @@ use SingleColorPetrinet\Model\PetrinetInterface; use SingleColorPetrinet\Service\GuardedTransitionService; use SingleColorPetrinet\Service\GuardedTransitionServiceInterface; +use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; use Tienvx\Bundle\MbtBundle\Entity\Task; @@ -294,7 +294,7 @@ public function testGetShortestPathBetweenSameSteps(): void public function testGetShortestPathFromCartEmptyToFourProductsInCart(): void { $this->expectsPetrinetHelper(); - $this->expectExceptionObject(new RuntimeException('Can not connect remaining steps')); + $this->expectExceptionObject(new UnrecoverableMessageHandlingException('Can not connect remaining steps')); iterator_to_array($this->stepsBuilder->create($this->bug, 0, 6)); } From 187063ec7202f67dba0052fcde51bb272c7fd2b9 Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 6 May 2022 10:11:41 +0700 Subject: [PATCH 53/85] Do nothing if steps not connected during reducing steps --- src/Exception/LogicException.php | 7 +++++++ src/Exception/StepsNotConnectedException.php | 7 +++++++ src/Reducer/HandlerTemplate.php | 8 +++++++- .../Step/Builder/ShortestPathStepsBuilder.php | 4 ++-- tests/Reducer/HandlerTestCase.php | 20 +++++++++++++++++++ .../Builder/ShortestPathStepsBuilderTest.php | 4 ++-- 6 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 src/Exception/LogicException.php create mode 100644 src/Exception/StepsNotConnectedException.php diff --git a/src/Exception/LogicException.php b/src/Exception/LogicException.php new file mode 100644 index 00000000..92570859 --- /dev/null +++ b/src/Exception/LogicException.php @@ -0,0 +1,7 @@ +stepsBuilder->create($bug, $from, $to)); + try { + $newSteps = iterator_to_array($this->stepsBuilder->create($bug, $from, $to)); + } catch (StepsNotConnectedException $exception) { + return; + } + if (count($newSteps) >= count($bug->getSteps())) { return; } diff --git a/src/Service/Step/Builder/ShortestPathStepsBuilder.php b/src/Service/Step/Builder/ShortestPathStepsBuilder.php index 038c7c05..5cdd29b8 100644 --- a/src/Service/Step/Builder/ShortestPathStepsBuilder.php +++ b/src/Service/Step/Builder/ShortestPathStepsBuilder.php @@ -6,9 +6,9 @@ use JMGQ\AStar\AStar; use SingleColorPetrinet\Model\PetrinetInterface; use SingleColorPetrinet\Service\GuardedTransitionServiceInterface; -use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Tienvx\Bundle\MbtBundle\Exception\ExceptionInterface; use Tienvx\Bundle\MbtBundle\Exception\OutOfRangeException; +use Tienvx\Bundle\MbtBundle\Exception\StepsNotConnectedException; use Tienvx\Bundle\MbtBundle\Model\Bug\Step; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; use Tienvx\Bundle\MbtBundle\Model\BugInterface; @@ -69,7 +69,7 @@ protected function getRemainingSteps(array $steps, StepInterface $lastStep, Petr } $transition = $petrinet->getTransitionById($step->getTransition()); if (!$this->transitionService->isEnabled($transition, $marking)) { - throw new UnrecoverableMessageHandlingException('Can not connect remaining steps'); + throw new StepsNotConnectedException('Can not connect remaining steps'); } $this->transitionService->fire($transition, $marking); yield new Step( diff --git a/tests/Reducer/HandlerTestCase.php b/tests/Reducer/HandlerTestCase.php index 6a496e0a..e902042a 100644 --- a/tests/Reducer/HandlerTestCase.php +++ b/tests/Reducer/HandlerTestCase.php @@ -3,6 +3,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Reducer; use Exception; +use PHPUnit\Framework\MockObject\Stub\Stub; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\MessageBusInterface; @@ -11,6 +12,7 @@ use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; use Tienvx\Bundle\MbtBundle\Entity\Task; use Tienvx\Bundle\MbtBundle\Exception\RuntimeException; +use Tienvx\Bundle\MbtBundle\Exception\StepsNotConnectedException; use Tienvx\Bundle\MbtBundle\Message\ReduceBugMessage; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; use Tienvx\Bundle\MbtBundle\Model\BugInterface; @@ -64,6 +66,13 @@ protected function setUp(): void ->willReturn((fn () => yield from $this->newSteps)()); } + public function testHandleNotConnectedSteps(): void + { + $this->expectStepsBuilder($this->throwException(new StepsNotConnectedException())); + $this->stepsRunner->expects($this->never())->method('run'); + $this->handler->handle($this->bug, 1, 2); + } + public function testHandleOldBug(): void { $this->bug->setSteps([ @@ -71,6 +80,7 @@ public function testHandleOldBug(): void $this->createMock(StepInterface::class), $this->createMock(StepInterface::class), ]); + $this->expectStepsBuilder($this->returnValue((fn () => yield from $this->newSteps)())); $this->stepsRunner->expects($this->never())->method('run'); $this->handler->handle($this->bug, 1, 2); } @@ -80,6 +90,7 @@ public function testHandleOldBug(): void */ public function testHandle(?Throwable $exception, bool $updateSteps): void { + $this->expectStepsBuilder($this->returnValue((fn () => yield from $this->newSteps)())); $this->stepsRunner->expects($this->once()) ->method('run') ->with( @@ -119,4 +130,13 @@ public function exceptionProvider(): array [new Exception('Something wrong'), true], ]; } + + protected function expectStepsBuilder(Stub $will): void + { + $this->stepsBuilder + ->expects($this->once()) + ->method('create') + ->with($this->bug, 1, 2) + ->will($will); + } } diff --git a/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php b/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php index 5898a5cd..cbd84e28 100644 --- a/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php +++ b/tests/Service/Step/Builder/ShortestPathStepsBuilderTest.php @@ -15,11 +15,11 @@ use SingleColorPetrinet\Model\PetrinetInterface; use SingleColorPetrinet\Service\GuardedTransitionService; use SingleColorPetrinet\Service\GuardedTransitionServiceInterface; -use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; use Tienvx\Bundle\MbtBundle\Entity\Task; use Tienvx\Bundle\MbtBundle\Exception\OutOfRangeException; +use Tienvx\Bundle\MbtBundle\Exception\StepsNotConnectedException; use Tienvx\Bundle\MbtBundle\Model\Bug\Step; use Tienvx\Bundle\MbtBundle\Service\Petrinet\MarkingHelper; use Tienvx\Bundle\MbtBundle\Service\Petrinet\MarkingHelperInterface; @@ -294,7 +294,7 @@ public function testGetShortestPathBetweenSameSteps(): void public function testGetShortestPathFromCartEmptyToFourProductsInCart(): void { $this->expectsPetrinetHelper(); - $this->expectExceptionObject(new UnrecoverableMessageHandlingException('Can not connect remaining steps')); + $this->expectExceptionObject(new StepsNotConnectedException('Can not connect remaining steps')); iterator_to_array($this->stepsBuilder->create($this->bug, 0, 6)); } From 017a4b58253a4ffd1e0051d58868f7f990ce2708 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 12 Jul 2022 23:35:36 +0700 Subject: [PATCH 54/85] Use assignments syntax for transition's expression --- composer.json | 4 ++-- src/ValueObject/Model/Transition.php | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 5f702cf2..5add83de 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,6 @@ "ext-json": "*", "doctrine/doctrine-bundle": "^2.6", "doctrine/orm": "^2.8", - "florianv/petrinet": "^2.1", "jmgq/a-star": "^2.1", "php-webdriver/webdriver": "^1.11", "symfony/config": "^5.4", @@ -29,7 +28,8 @@ "symfony/http-kernel": "^5.4", "symfony/messenger": "^5.4", "symfony/validator": "^5.4", - "tienvx/single-color-petrinet": "^1.5" + "tienvx/single-color-petrinet": "^1.5", + "tienvx/assignments-evaluator-bundle": "^1.0" }, "require-dev": { "phpunit/phpunit": "^9.5" diff --git a/src/ValueObject/Model/Transition.php b/src/ValueObject/Model/Transition.php index 242b07db..d6ea4399 100644 --- a/src/ValueObject/Model/Transition.php +++ b/src/ValueObject/Model/Transition.php @@ -3,6 +3,7 @@ namespace Tienvx\Bundle\MbtBundle\ValueObject\Model; use Symfony\Component\Validator\Constraints as Assert; +use Tienvx\Bundle\AssignmentsEvaluatorBundle\Validator\AssignmentsSyntax; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\Transition as TransitionModel; class Transition extends TransitionModel @@ -23,7 +24,7 @@ class Transition extends TransitionModel /** * @Assert\AtLeastOneOf({ * @Assert\IsNull, - * @Assert\ExpressionLanguageSyntax + * @AssignmentsSyntax * }) */ protected ?string $expression = null; From 763ca307f4989f6c9c20288196967a009bf9eff6 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 12 Jul 2022 23:40:02 +0700 Subject: [PATCH 55/85] Fix cs with latest php cs fixer --- src/Command/CommandRunner.php | 2 +- src/Factory/Model/Revision/PlaceFactory.php | 2 +- src/Factory/Model/Revision/TransitionFactory.php | 2 +- tests/Service/Bug/BugHelperTest.php | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Command/CommandRunner.php b/src/Command/CommandRunner.php index 1ce9b730..78858460 100644 --- a/src/Command/CommandRunner.php +++ b/src/Command/CommandRunner.php @@ -33,7 +33,7 @@ protected function getSelector(string $target): WebDriverBy case static::MECHANISM_LINK_TEXT: case static::MECHANISM_PARTIAL_LINK_TEXT: case static::MECHANISM_XPATH: - return WebDriverBy::{$mechanism}($value); + return WebDriverBy::$mechanism($value); case static::MECHANISM_CSS: return WebDriverBy::cssSelector($value); default: diff --git a/src/Factory/Model/Revision/PlaceFactory.php b/src/Factory/Model/Revision/PlaceFactory.php index fe32c098..ffba1e4d 100644 --- a/src/Factory/Model/Revision/PlaceFactory.php +++ b/src/Factory/Model/Revision/PlaceFactory.php @@ -12,7 +12,7 @@ public static function createFromArray(array $data): PlaceInterface $place = new Place(); $place->setLabel($data['label'] ?? ''); $place->setCommands( - array_map([CommandFactory::class, 'createFromArray'], ($data['commands'] ?? [])) + array_map([CommandFactory::class, 'createFromArray'], $data['commands'] ?? []) ); return $place; diff --git a/src/Factory/Model/Revision/TransitionFactory.php b/src/Factory/Model/Revision/TransitionFactory.php index ca89d9bd..704b8224 100644 --- a/src/Factory/Model/Revision/TransitionFactory.php +++ b/src/Factory/Model/Revision/TransitionFactory.php @@ -14,7 +14,7 @@ public static function createFromArray(array $data): TransitionInterface $transition->setGuard($data['guard'] ?? null); $transition->setExpression($data['expression'] ?? null); $transition->setCommands( - array_map([CommandFactory::class, 'createFromArray'], ($data['commands'] ?? [])) + array_map([CommandFactory::class, 'createFromArray'], $data['commands'] ?? []) ); $transition->setFromPlaces($data['fromPlaces'] ?? []); $transition->setToPlaces($data['toPlaces'] ?? []); diff --git a/tests/Service/Bug/BugHelperTest.php b/tests/Service/Bug/BugHelperTest.php index ae205a27..63596ba5 100644 --- a/tests/Service/Bug/BugHelperTest.php +++ b/tests/Service/Bug/BugHelperTest.php @@ -134,9 +134,9 @@ public function testReduceBugRecordAndReport(): void ->method('dispatch') ->with($this->callback(function ($message) { return ( - $message instanceof RecordVideoMessage - || $message instanceof ReportBugMessage - ) + $message instanceof RecordVideoMessage + || $message instanceof ReportBugMessage + ) && 123 === $message->getBugId(); })) ->willReturn(new Envelope(new \stdClass())); From 06ad0b99835e251ab1cbd1d288ba4a590673b98b Mon Sep 17 00:00:00 2001 From: tienvx Date: Wed, 13 Jul 2022 00:01:01 +0700 Subject: [PATCH 56/85] Use correct assignments syntax in test --- tests/Model/Model/RevisionTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Model/Model/RevisionTest.php b/tests/Model/Model/RevisionTest.php index aca7e1ce..20ab245b 100644 --- a/tests/Model/Model/RevisionTest.php +++ b/tests/Model/Model/RevisionTest.php @@ -70,7 +70,7 @@ protected function setUp(): void $t1->setLabel('t1'); $t1->setFromPlaces([1]); $t1->setToPlaces([1, 2]); - $t1->setExpression('{count: count + 1}'); + $t1->setExpression('count = count + 1'); $t2->setLabel(''); $t2->setFromPlaces([1, 2]); $t2->setToPlaces([]); @@ -144,7 +144,7 @@ public function testToArray(): void 0 => [ 'label' => 't1', 'guard' => null, - 'expression' => '{count: count + 1}', + 'expression' => 'count = count + 1', 'fromPlaces' => [ 0 => 1, ], From c73b6eb93e2bcd3fc3735f1f186390230e5a3b27 Mon Sep 17 00:00:00 2001 From: tienvx Date: Wed, 13 Jul 2022 19:10:04 +0700 Subject: [PATCH 57/85] Fix expression syntax error --- src/Resources/config/services.php | 2 ++ src/Service/Petrinet/PetrinetHelper.php | 12 +++++++++--- .../Factory/Model/Revision/TransitionFactoryTest.php | 4 ++-- tests/Generator/RandomGeneratorTest.php | 4 +++- tests/Model/Model/Revision/TransitionTest.php | 8 ++++---- tests/Service/Petrinet/PetrinetHelperTest.php | 6 ++++-- 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index bafa7aed..2f6c2c3f 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -9,6 +9,7 @@ use SingleColorPetrinet\Service\GuardedTransitionService; use SingleColorPetrinet\Service\GuardedTransitionServiceInterface; use Symfony\Component\Messenger\MessageBusInterface; +use Tienvx\AssignmentsEvaluator\AssignmentsEvaluator; use Tienvx\Bundle\MbtBundle\Channel\ChannelManager; use Tienvx\Bundle\MbtBundle\Channel\ChannelManagerInterface; use Tienvx\Bundle\MbtBundle\Command\CommandPreprocessor; @@ -278,6 +279,7 @@ ->args([ service(ColorfulFactoryInterface::class), service(ExpressionLanguage::class), + service(AssignmentsEvaluator::class), ]) ->alias(PetrinetHelperInterface::class, PetrinetHelper::class) diff --git a/src/Service/Petrinet/PetrinetHelper.php b/src/Service/Petrinet/PetrinetHelper.php index 5da51f00..45907ec5 100644 --- a/src/Service/Petrinet/PetrinetHelper.php +++ b/src/Service/Petrinet/PetrinetHelper.php @@ -8,6 +8,7 @@ use SingleColorPetrinet\Model\ColorfulFactoryInterface; use SingleColorPetrinet\Model\ColorInterface; use SingleColorPetrinet\Model\PetrinetInterface; +use Tienvx\AssignmentsEvaluator\AssignmentsEvaluator; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\PlaceInterface; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\TransitionInterface; use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; @@ -19,11 +20,16 @@ class PetrinetHelper implements PetrinetHelperInterface protected ColorfulFactoryInterface $colorfulFactory; protected ExpressionLanguage $expressionLanguage; + protected AssignmentsEvaluator $assignmentsEvaluator; - public function __construct(ColorfulFactoryInterface $colorfulFactory, ExpressionLanguage $expressionLanguage) - { + public function __construct( + ColorfulFactoryInterface $colorfulFactory, + ExpressionLanguage $expressionLanguage, + AssignmentsEvaluator $assignmentsEvaluator + ) { $this->colorfulFactory = $colorfulFactory; $this->expressionLanguage = $expressionLanguage; + $this->assignmentsEvaluator = $assignmentsEvaluator; } public function build(RevisionInterface $revision): PetrinetInterface @@ -75,7 +81,7 @@ protected function getTransitions(RevisionInterface $revision, SingleColorPetrin ) : null, $transition->getExpression() - ? fn (ColorInterface $color): array => (array) $this->expressionLanguage->evaluate( + ? fn (ColorInterface $color): array => $this->assignmentsEvaluator->evaluate( $transition->getExpression(), $color->getValues() ) diff --git a/tests/Factory/Model/Revision/TransitionFactoryTest.php b/tests/Factory/Model/Revision/TransitionFactoryTest.php index b97a2e95..24f71d5b 100644 --- a/tests/Factory/Model/Revision/TransitionFactoryTest.php +++ b/tests/Factory/Model/Revision/TransitionFactoryTest.php @@ -20,7 +20,7 @@ protected function setUp(): void $this->data = [ 'label' => 'Transition 1', 'guard' => 'count > 1', - 'expression' => '{count: count + 1}', + 'expression' => 'count = count + 1', 'fromPlaces' => [1, 2], 'toPlaces' => [2, 3], 'commands' => [], @@ -32,7 +32,7 @@ public function testCreateFromArray(): void $transition = TransitionFactory::createFromArray($this->data); $this->assertSame('Transition 1', $transition->getLabel()); $this->assertSame('count > 1', $transition->getGuard()); - $this->assertSame('{count: count + 1}', $transition->getExpression()); + $this->assertSame('count = count + 1', $transition->getExpression()); $this->assertSame([1, 2], $transition->getFromPlaces()); $this->assertSame([2, 3], $transition->getToPlaces()); $this->assertIsArray($transition->getCommands()); diff --git a/tests/Generator/RandomGeneratorTest.php b/tests/Generator/RandomGeneratorTest.php index ee821b94..8f51bcbf 100644 --- a/tests/Generator/RandomGeneratorTest.php +++ b/tests/Generator/RandomGeneratorTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\TestCase; use SingleColorPetrinet\Model\ColorfulFactory; use SingleColorPetrinet\Service\GuardedTransitionService; +use Tienvx\AssignmentsEvaluator\AssignmentsEvaluator; use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; use Tienvx\Bundle\MbtBundle\Entity\Task; use Tienvx\Bundle\MbtBundle\Generator\GeneratorManager; @@ -45,7 +46,8 @@ protected function setUp(): void { $factory = new ColorfulFactory(); $expressionLanguage = new ExpressionLanguage(); - $petrinetHelper = new PetrinetHelper($factory, $expressionLanguage); + $assignmentsEvaluator = new AssignmentsEvaluator($expressionLanguage); + $petrinetHelper = new PetrinetHelper($factory, $expressionLanguage, $assignmentsEvaluator); $markingHelper = new MarkingHelper($factory); $modelHelper = new ModelHelper(); $transitionService = new GuardedTransitionService($factory); diff --git a/tests/Model/Model/Revision/TransitionTest.php b/tests/Model/Model/Revision/TransitionTest.php index a1a945a2..748ac29a 100644 --- a/tests/Model/Model/Revision/TransitionTest.php +++ b/tests/Model/Model/Revision/TransitionTest.php @@ -29,7 +29,7 @@ protected function setUp(): void $this->transition = $this->createTransition(); $this->transition->setLabel('transition label'); $this->transition->setGuard('count > 2'); - $this->transition->setExpression('{count: count + 1}'); + $this->transition->setExpression('count = count + 1'); $this->transition->setFromPlaces([1, 2, 3]); $this->transition->setToPlaces([12, 23]); $this->transition->setCommands([ @@ -61,18 +61,18 @@ public function testSerialize(): void { $className = get_class($this->transition); // phpcs:ignore Generic.Files.LineLength - $this->assertSame('O:' . strlen($className) . ':"' . $className . '":6:{s:5:"label";s:16:"transition label";s:5:"guard";s:9:"count > 2";s:10:"expression";s:18:"{count: count + 1}";s:10:"fromPlaces";a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}s:8:"toPlaces";a:2:{i:0;i:12;i:1;i:23;}s:8:"commands";a:2:{i:0;a:3:{s:7:"command";s:4:"type";s:6:"target";s:10:"css=.email";s:5:"value";s:16:"test@example.com";}i:1;a:3:{s:7:"command";s:5:"click";s:6:"target";s:9:"css=.link";s:5:"value";N;}}}', serialize($this->transition)); + $this->assertSame('O:' . strlen($className) . ':"' . $className . '":6:{s:5:"label";s:16:"transition label";s:5:"guard";s:9:"count > 2";s:10:"expression";s:17:"count = count + 1";s:10:"fromPlaces";a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}s:8:"toPlaces";a:2:{i:0;i:12;i:1;i:23;}s:8:"commands";a:2:{i:0;a:3:{s:7:"command";s:4:"type";s:6:"target";s:10:"css=.email";s:5:"value";s:16:"test@example.com";}i:1;a:3:{s:7:"command";s:5:"click";s:6:"target";s:9:"css=.link";s:5:"value";N;}}}', serialize($this->transition)); } public function testUnerialize(): void { $className = get_class($this->transition); // phpcs:ignore Generic.Files.LineLength - $transition = unserialize('O:' . strlen($className) . ':"' . $className . '":6:{s:5:"label";s:10:"Serialized";s:5:"guard";s:10:"count == 3";s:10:"expression";s:18:"{count: count - 1}";s:10:"fromPlaces";a:2:{i:0;i:1;i:1;i:4;}s:8:"toPlaces";a:1:{i:0;i:15;}s:8:"commands";a:1:{i:0;a:3:{s:7:"command";s:5:"store";s:6:"target";s:2:"55";s:5:"value";s:6:"number";}}}'); + $transition = unserialize('O:' . strlen($className) . ':"' . $className . '":6:{s:5:"label";s:10:"Serialized";s:5:"guard";s:10:"count == 3";s:10:"expression";s:17:"count = count - 1";s:10:"fromPlaces";a:2:{i:0;i:1;i:1;i:4;}s:8:"toPlaces";a:1:{i:0;i:15;}s:8:"commands";a:1:{i:0;a:3:{s:7:"command";s:5:"store";s:6:"target";s:2:"55";s:5:"value";s:6:"number";}}}'); $this->assertInstanceOf(TransitionInterface::class, $transition); $this->assertSame('Serialized', $transition->getLabel()); $this->assertSame('count == 3', $transition->getGuard()); - $this->assertSame('{count: count - 1}', $transition->getExpression()); + $this->assertSame('count = count - 1', $transition->getExpression()); $this->assertSame([1, 4], $transition->getFromPlaces()); $this->assertSame([15], $transition->getToPlaces()); $this->assertInstanceOf(CommandInterface::class, $transition->getCommands()[0]); diff --git a/tests/Service/Petrinet/PetrinetHelperTest.php b/tests/Service/Petrinet/PetrinetHelperTest.php index 6115a2f7..b6599f9f 100644 --- a/tests/Service/Petrinet/PetrinetHelperTest.php +++ b/tests/Service/Petrinet/PetrinetHelperTest.php @@ -8,6 +8,7 @@ use SingleColorPetrinet\Model\ColorfulFactory; use SingleColorPetrinet\Model\GuardedTransition as PetrinetTransition; use SingleColorPetrinet\Model\Place as PetrinetPlace; +use Tienvx\AssignmentsEvaluator\AssignmentsEvaluator; use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; use Tienvx\Bundle\MbtBundle\Service\ExpressionLanguage; use Tienvx\Bundle\MbtBundle\Service\Petrinet\PetrinetHelper; @@ -28,7 +29,8 @@ public function testBuild(): void { $factory = new ColorfulFactory(); $expressionLanguage = new ExpressionLanguage(); - $helper = new PetrinetHelper($factory, $expressionLanguage); + $assignmentsEvaluator = new AssignmentsEvaluator($expressionLanguage); + $helper = new PetrinetHelper($factory, $expressionLanguage, $assignmentsEvaluator); // Model revision $revision = new Revision(); @@ -50,7 +52,7 @@ public function testBuild(): void $transition1->setToPlaces([1, 2]); $transition2->setFromPlaces([2]); $transition2->setToPlaces([1]); - $transition3->setExpression('{count: count + 1, status: "open"}'); + $transition3->setExpression('count = count + 1; status = "open"'); $transition3->setFromPlaces([1]); $transition3->setToPlaces([0, 2]); From 08a31a2e346490df1174ea32cb5521ed2c8eb2ad Mon Sep 17 00:00:00 2001 From: tienvx Date: Wed, 13 Jul 2022 21:13:51 +0700 Subject: [PATCH 58/85] Override expression language --- src/Resources/config/services.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index 2f6c2c3f..e7e6e956 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -213,6 +213,7 @@ // Services ->set(ExpressionLanguage::class) + ->set('assignments_evaluator.expression_language', ExpressionLanguage::class) ->set(SelenoidHelper::class) ->alias(SelenoidHelperInterface::class, SelenoidHelper::class) From 3beed74419d3681e135936c2d39df153d610202d Mon Sep 17 00:00:00 2001 From: tienvx Date: Thu, 14 Jul 2022 22:54:34 +0700 Subject: [PATCH 59/85] Upload command --- src/Command/CommandRunner.php | 5 ++ src/Command/Runner/CustomCommandRunner.php | 54 ++++++++++++++++++++ src/Command/Runner/KeyboardCommandRunner.php | 5 -- src/Command/Runner/MouseCommandRunner.php | 5 -- src/Command/Runner/WaitCommandRunner.php | 5 -- src/Resources/config/services.php | 3 ++ 6 files changed, 62 insertions(+), 15 deletions(-) create mode 100644 src/Command/Runner/CustomCommandRunner.php diff --git a/src/Command/CommandRunner.php b/src/Command/CommandRunner.php index 78858460..8ad4cb70 100644 --- a/src/Command/CommandRunner.php +++ b/src/Command/CommandRunner.php @@ -58,4 +58,9 @@ protected function getSelect(WebDriverElement $element): WebDriverSelect { return new WebDriverSelect($element); } + + public function validateTarget(CommandInterface $command): bool + { + return $command->getTarget() && $this->isValidSelector($command->getTarget()); + } } diff --git a/src/Command/Runner/CustomCommandRunner.php b/src/Command/Runner/CustomCommandRunner.php new file mode 100644 index 00000000..aa9d9395 --- /dev/null +++ b/src/Command/Runner/CustomCommandRunner.php @@ -0,0 +1,54 @@ +getAllCommands(); + } + + public function getCommandsRequireValue(): array + { + return $this->getAllCommands(); + } + + public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void + { + switch ($command->getCommand()) { + case self::UPLOAD: + $driver + ->findElement($this->getSelector($command->getTarget())) + ->setFileDetector(new LocalFileDetector()) + ->sendKeys($this->getFilePath($command)); + break; + default: + break; + } + } + + protected function getFilePath(CommandInterface $command): string + { + return $this->uploadDir . DIRECTORY_SEPARATOR . (string) $command->getValue(); + } +} diff --git a/src/Command/Runner/KeyboardCommandRunner.php b/src/Command/Runner/KeyboardCommandRunner.php index 21ef7a38..4ffb072a 100644 --- a/src/Command/Runner/KeyboardCommandRunner.php +++ b/src/Command/Runner/KeyboardCommandRunner.php @@ -51,11 +51,6 @@ public function run(CommandInterface $command, ValuesInterface $values, RemoteWe } } - public function validateTarget(CommandInterface $command): bool - { - return $command->getTarget() && $this->isValidSelector($command->getTarget()); - } - /** * Don't allow to upload local file. */ diff --git a/src/Command/Runner/MouseCommandRunner.php b/src/Command/Runner/MouseCommandRunner.php index 8a2e5e60..5ac4be08 100644 --- a/src/Command/Runner/MouseCommandRunner.php +++ b/src/Command/Runner/MouseCommandRunner.php @@ -219,11 +219,6 @@ public function run(CommandInterface $command, ValuesInterface $values, RemoteWe } } - public function validateTarget(CommandInterface $command): bool - { - return $command->getTarget() && $this->isValidSelector($command->getTarget()); - } - protected function getPoint(string $target): WebDriverPoint { list($x, $y) = explode(',', $target); diff --git a/src/Command/Runner/WaitCommandRunner.php b/src/Command/Runner/WaitCommandRunner.php index 6873fa3b..8ac874f0 100644 --- a/src/Command/Runner/WaitCommandRunner.php +++ b/src/Command/Runner/WaitCommandRunner.php @@ -87,9 +87,4 @@ public function run(CommandInterface $command, ValuesInterface $values, RemoteWe break; } } - - public function validateTarget(CommandInterface $command): bool - { - return $command->getTarget() && $this->isValidSelector($command->getTarget()); - } } diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index e7e6e956..906fbb9a 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -19,6 +19,7 @@ use Tienvx\Bundle\MbtBundle\Command\CommandRunnerManagerInterface; use Tienvx\Bundle\MbtBundle\Command\Runner\AlertCommandRunner; use Tienvx\Bundle\MbtBundle\Command\Runner\AssertionRunner; +use Tienvx\Bundle\MbtBundle\Command\Runner\CustomCommandRunner; use Tienvx\Bundle\MbtBundle\Command\Runner\KeyboardCommandRunner; use Tienvx\Bundle\MbtBundle\Command\Runner\MouseCommandRunner; use Tienvx\Bundle\MbtBundle\Command\Runner\ScriptCommandRunner; @@ -196,6 +197,8 @@ ->autoconfigure(true) ->set(WindowCommandRunner::class) ->autoconfigure(true) + ->set(CustomCommandRunner::class) + ->autoconfigure(true) // Repositories ->set(BugRepository::class) From 92bacf7f2a2078c6c9abce28fc00d156d851a5e7 Mon Sep 17 00:00:00 2001 From: tienvx Date: Thu, 14 Jul 2022 23:23:43 +0700 Subject: [PATCH 60/85] Support Symfony 6 --- .github/workflows/main.yml | 14 ++++++++------ composer.json | 12 ++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5b74e9ce..61e63373 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,22 +7,24 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['8.0'] - name: PHP ${{ matrix.php-versions }} + php-versions: ['8.0', '8.1'] + dependency-versions: ['lowest', 'highest'] + name: PHP ${{ matrix.php-versions }} with ${{ matrix.dependency-versions }} versions of Composer dependencies steps: - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} coverage: pcov - tools: phpstan, phpcs, php-cs-fixer:3, phpunit, composer:v2 - #extensions: mbstring, intl + tools: phpstan, phpcs, php-cs-fixer:3 - name: Checkout uses: actions/checkout@v2 - name: Install - run: composer install + uses: "ramsey/composer-install@v2" + with: + dependency-versions: ${{ matrix.dependency-versions }} - name: Run PHP CS run: phpcs --standard=PSR12 src tests @@ -34,7 +36,7 @@ jobs: run: phpstan analyse src tests - name: Test & Generate Code Coverage - run: phpunit + run: ./vendor/bin/phpunit - name: Upload coverage results to Coveralls env: diff --git a/composer.json b/composer.json index 5add83de..8d481cf0 100644 --- a/composer.json +++ b/composer.json @@ -22,12 +22,12 @@ "doctrine/orm": "^2.8", "jmgq/a-star": "^2.1", "php-webdriver/webdriver": "^1.11", - "symfony/config": "^5.4", - "symfony/dependency-injection": "^5.4", - "symfony/expression-language": "^5.4", - "symfony/http-kernel": "^5.4", - "symfony/messenger": "^5.4", - "symfony/validator": "^5.4", + "symfony/config": "^5.4|^6.1", + "symfony/dependency-injection": "^5.4|^6.1", + "symfony/expression-language": "^5.4|^6.1", + "symfony/http-kernel": "^5.4|^6.1", + "symfony/messenger": "^5.4|^6.1", + "symfony/validator": "^5.4|^6.1", "tienvx/single-color-petrinet": "^1.5", "tienvx/assignments-evaluator-bundle": "^1.0" }, From 3a423cdc5b716c93bc90a55bb10edd019b83b36a Mon Sep 17 00:00:00 2001 From: tienvx Date: Thu, 14 Jul 2022 23:53:50 +0700 Subject: [PATCH 61/85] Fix return type is not covariant --- tests/Fixtures/Validator/CustomConstraintValidatorFactory.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Fixtures/Validator/CustomConstraintValidatorFactory.php b/tests/Fixtures/Validator/CustomConstraintValidatorFactory.php index 15668e30..7f5ac694 100644 --- a/tests/Fixtures/Validator/CustomConstraintValidatorFactory.php +++ b/tests/Fixtures/Validator/CustomConstraintValidatorFactory.php @@ -4,6 +4,7 @@ use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidatorFactory; +use Symfony\Component\Validator\ConstraintValidatorInterface; use Tienvx\Bundle\MbtBundle\Command\CommandPreprocessor; use Tienvx\Bundle\MbtBundle\Command\CommandRunnerManager; use Tienvx\Bundle\MbtBundle\Command\Runner\AlertCommandRunner; @@ -18,7 +19,7 @@ class CustomConstraintValidatorFactory extends ConstraintValidatorFactory { - public function getInstance(Constraint $constraint) + public function getInstance(Constraint $constraint): ConstraintValidatorInterface { $className = $constraint->validatedBy(); if (ValidCommandValidator::class === $className && !isset($this->validators[$className])) { From 24a74012aa4c883090edf709b699843e3dda7f9b Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 15 Jul 2022 00:35:57 +0700 Subject: [PATCH 62/85] Limit to PHP 8.1 and use nested attributes to fix validation tests failed --- .github/workflows/main.yml | 2 +- composer.json | 2 +- src/Entity/Bug.php | 71 +++++++++------------------ src/Entity/Bug/Video.php | 12 ++--- src/Entity/Model.php | 61 ++++++++--------------- src/Entity/Model/Revision.php | 58 +++++++++------------- src/Entity/Progress.php | 20 +++----- src/Entity/Task.php | 72 ++++++++++------------------ src/Entity/Task/Browser.php | 16 ++----- src/Validator/Tags.php | 4 +- src/Validator/ValidCommand.php | 6 +-- src/ValueObject/Bug/Step.php | 19 +++----- src/ValueObject/Model/Command.php | 8 +--- src/ValueObject/Model/Place.php | 14 ++---- src/ValueObject/Model/Transition.php | 52 ++++++++------------ 15 files changed, 143 insertions(+), 274 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 61e63373..9b2c5211 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['8.0', '8.1'] + php-versions: ['8.1'] dependency-versions: ['lowest', 'highest'] name: PHP ${{ matrix.php-versions }} with ${{ matrix.dependency-versions }} versions of Composer dependencies steps: diff --git a/composer.json b/composer.json index 8d481cf0..6cb3b81b 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": "^8.0", + "php": "^8.1", "ext-json": "*", "doctrine/doctrine-bundle": "^2.6", "doctrine/orm": "^2.8", diff --git a/src/Entity/Bug.php b/src/Entity/Bug.php index 564020e7..1159556d 100644 --- a/src/Entity/Bug.php +++ b/src/Entity/Bug.php @@ -12,69 +12,48 @@ use Tienvx\Bundle\MbtBundle\Model\ProgressInterface; use Tienvx\Bundle\MbtBundle\Model\TaskInterface; use Tienvx\Bundle\MbtBundle\Repository\BugRepository; +use Tienvx\Bundle\MbtBundle\ValueObject\Bug\Step; -/** - * @ORM\Entity(repositoryClass=BugRepository::class) - * @ORM\HasLifecycleCallbacks - */ +#[ORM\Entity(repositoryClass: BugRepository::class)] +#[ORM\HasLifecycleCallbacks] class Bug extends BugModel { - /** - * @ORM\Column(type="integer") - * @ORM\Id() - * @ORM\GeneratedValue(strategy="AUTO") - */ + #[ORM\Column(type: 'integer')] + #[ORM\Id] + #[ORM\GeneratedValue(strategy: 'AUTO')] protected ?int $id = null; - /** - * @ORM\Column(type="string") - * @Assert\NotBlank - */ + #[ORM\Column(type: 'string')] + #[Assert\NotBlank] protected string $title; - /** - * @ORM\Column(type="array") - * @Assert\All({ - * @Assert\Type("\Tienvx\Bundle\MbtBundle\ValueObject\Bug\Step") - * }) - * @Assert\Valid - */ + #[ORM\Column(type: 'array')] + #[Assert\All([ + new Assert\Type(type: Step::class), + ])] + #[Assert\Valid] protected array $steps = []; - /** - * @ORM\ManyToOne(targetEntity="\Tienvx\Bundle\MbtBundle\Entity\Task", inversedBy="bugs") - * @ORM\JoinColumn(nullable=false) - */ + #[ORM\ManyToOne(targetEntity: Task::class, inversedBy: 'bugs')] + #[ORM\JoinColumn(nullable: false)] protected TaskInterface $task; - /** - * @ORM\Column(type="text") - */ + #[ORM\Column(type: 'text')] protected string $message; - /** - * @ORM\Embedded(class="Progress") - */ + #[ORM\Embedded(class: Progress::class)] protected ProgressInterface $progress; - /** - * @ORM\Column(type="boolean") - */ + #[ORM\Column(type: 'boolean')] protected bool $closed = false; - /** - * @ORM\Embedded(class="\Tienvx\Bundle\MbtBundle\Entity\Bug\Video") - */ + #[ORM\Embedded(class: Video::class)] protected VideoInterface $video; - /** - * @ORM\Column(name="created_at", type="datetime") - */ + #[ORM\Column(name: 'created_at', type: 'datetime')] protected DateTimeInterface $createdAt; - /** - * @ORM\Column(name="updated_at", type="datetime") - */ + #[ORM\Column(name: 'updated_at', type: 'datetime')] protected DateTimeInterface $updatedAt; public function __construct() @@ -84,18 +63,14 @@ public function __construct() $this->video = new Video(); } - /** - * @ORM\PrePersist - */ + #[ORM\PrePersist] public function prePersist(): void { $this->setCreatedAt(new DateTime()); $this->setUpdatedAt(new DateTime()); } - /** - * @ORM\PreUpdate - */ + #[ORM\PreUpdate] public function preUpdate(): void { $this->setUpdatedAt(new DateTime()); diff --git a/src/Entity/Bug/Video.php b/src/Entity/Bug/Video.php index cfae14bd..ad708086 100644 --- a/src/Entity/Bug/Video.php +++ b/src/Entity/Bug/Video.php @@ -6,18 +6,12 @@ use Doctrine\ORM\Mapping\Embeddable; use Tienvx\Bundle\MbtBundle\Model\Bug\Video as VideoModel; -/** - * @Embeddable - */ +#[Embeddable] class Video extends VideoModel { - /** - * @ORM\Column(type="boolean") - */ + #[ORM\Column(type: 'boolean')] protected bool $recording = false; - /** - * @ORM\Column(type="text", nullable=true) - */ + #[ORM\Column(type: 'text', nullable: true)] protected ?string $errorMessage = null; } diff --git a/src/Entity/Model.php b/src/Entity/Model.php index 1fe43d83..79a37093 100644 --- a/src/Entity/Model.php +++ b/src/Entity/Model.php @@ -7,75 +7,54 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; +use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; use Tienvx\Bundle\MbtBundle\Model\Model as BaseModel; use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; use Tienvx\Bundle\MbtBundle\Validator\Tags; -/** - * @ORM\Entity - * @ORM\HasLifecycleCallbacks - */ +#[ORM\Entity] +#[ORM\HasLifecycleCallbacks] class Model extends BaseModel { - /** - * @ORM\Id - * @ORM\GeneratedValue - * @ORM\Column(type="integer") - */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: 'integer')] protected ?int $id = null; - /** - * @ORM\Column(type="integer", nullable=true) - */ + #[ORM\Column(type: 'integer', nullable: true)] protected ?int $author = null; - /** - * @ORM\Column(type="string") - * @Assert\NotBlank - */ + #[ORM\Column(type: 'string')] + #[Assert\NotBlank] protected string $label = ''; - /** - * @ORM\Column(type="string", nullable=true) - * @Tags - */ + #[ORM\Column(type: 'string', nullable: true)] + #[Tags] protected ?string $tags = null; - /** - * @ORM\Column(name="created_at", type="datetime") - */ + #[ORM\Column(name: 'created_at', type: 'datetime')] protected DateTimeInterface $createdAt; - /** - * @ORM\Column(name="updated_at", type="datetime") - */ + #[ORM\Column(name: 'updated_at', type: 'datetime')] protected DateTimeInterface $updatedAt; - /** - * @ORM\OneToOne(targetEntity="Tienvx\Bundle\MbtBundle\Entity\Model\Revision", cascade={"persist"}) - * @ORM\JoinColumn(nullable=false) - * @Assert\Valid - */ + #[ORM\OneToOne(targetEntity: Revision::class, cascade: ['persist'])] + #[ORM\JoinColumn(nullable: false)] + #[Assert\Valid] protected RevisionInterface $activeRevision; - /** - * @ORM\OneToMany(targetEntity="Tienvx\Bundle\MbtBundle\Entity\Model\Revision", mappedBy="model") - * @Assert\Valid - */ + #[ORM\OneToMany(targetEntity: Revision::class, mappedBy: 'model')] + #[Assert\Valid] protected Collection $revisions; - /** - * @ORM\PrePersist - */ + #[ORM\PrePersist] public function prePersist(): void { $this->setCreatedAt(new DateTime()); $this->setUpdatedAt(new DateTime()); } - /** - * @ORM\PreUpdate - */ + #[ORM\PreUpdate] public function preUpdate(): void { $this->setUpdatedAt(new DateTime()); diff --git a/src/Entity/Model/Revision.php b/src/Entity/Model/Revision.php index b758773a..d0e9653b 100644 --- a/src/Entity/Model/Revision.php +++ b/src/Entity/Model/Revision.php @@ -5,51 +5,42 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Tienvx\Bundle\MbtBundle\Entity\Model; use Tienvx\Bundle\MbtBundle\Model\Model\Revision as BaseRevision; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\TransitionInterface; use Tienvx\Bundle\MbtBundle\Model\ModelInterface; +use Tienvx\Bundle\MbtBundle\ValueObject\Model\Place; +use Tienvx\Bundle\MbtBundle\ValueObject\Model\Transition; -/** - * @ORM\Entity - */ +#[ORM\Entity] class Revision extends BaseRevision { - /** - * @ORM\Id - * @ORM\GeneratedValue - * @ORM\Column(type="integer") - */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: 'integer')] protected ?int $id = null; - /** - * @ORM\ManyToOne(targetEntity="Tienvx\Bundle\MbtBundle\Entity\Model", inversedBy="revisions") - * @ORM\JoinColumn(nullable=true, onDelete="SET NULL") - */ + #[ORM\ManyToOne(targetEntity: Model::class, inversedBy: 'revisions')] + #[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')] protected ?ModelInterface $model = null; - /** - * @ORM\Column(type="array") - * @Assert\All({ - * @Assert\Type("\Tienvx\Bundle\MbtBundle\ValueObject\Model\Place") - * }) - * @Assert\Valid - * @Assert\Count(min=1) - */ + #[ORM\Column(type: 'array')] + #[Assert\All([ + new Assert\Type(type: Place::class), + ])] + #[Assert\Valid] + #[Assert\Count(min: 1)] protected array $places = []; - /** - * @ORM\Column(type="array") - * @Assert\All({ - * @Assert\Type("\Tienvx\Bundle\MbtBundle\ValueObject\Model\Transition") - * }) - * @Assert\Valid - * @Assert\Count(min=1) - */ + #[ORM\Column(type: 'array')] + #[Assert\All([ + new Assert\Type(type: Transition::class), + ])] + #[Assert\Valid] + #[Assert\Count(min: 1)] protected array $transitions = []; - /** - * @Assert\Callback - */ + #[Assert\Callback] public function validatePlacesInTransitions(ExecutionContextInterface $context, $payload): void { foreach ($this->transitions as $index => $transition) { @@ -75,15 +66,12 @@ public function validatePlacesInTransitions(ExecutionContextInterface $context, } } - /** - * @Assert\Callback - */ + #[Assert\Callback] public function validateStartTransitions(ExecutionContextInterface $context, $payload): void { if (0 === count($this->transitions)) { return; } - $startTransitions = array_filter( $this->transitions, fn ($transition) => $transition instanceof TransitionInterface && 0 === count($transition->getFromPlaces()) diff --git a/src/Entity/Progress.php b/src/Entity/Progress.php index b477928d..a0e3cbe7 100644 --- a/src/Entity/Progress.php +++ b/src/Entity/Progress.php @@ -7,26 +7,18 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; use Tienvx\Bundle\MbtBundle\Model\Progress as ProgressModel; -/** - * @ORM\Embeddable - */ +#[ORM\Embeddable] class Progress extends ProgressModel { - /** - * @ORM\Column(type="integer", options={"default": 0}) - * @Assert\Positive - */ + #[ORM\Column(type: 'integer', options: ['default' => 0])] + #[Assert\Positive] protected int $total = 0; - /** - * @ORM\Column(type="integer", options={"default": 0}) - * @Assert\Positive - */ + #[ORM\Column(type: 'integer', options: ['default' => 0])] + #[Assert\Positive] protected int $processed = 0; - /** - * @Assert\Callback - */ + #[Assert\Callback] public function validate(ExecutionContextInterface $context, $payload) { if ($this->processed > $this->total) { diff --git a/src/Entity/Task.php b/src/Entity/Task.php index 514abae8..21794326 100644 --- a/src/Entity/Task.php +++ b/src/Entity/Task.php @@ -8,87 +8,63 @@ use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping\Embedded; use Symfony\Component\Validator\Constraints as Assert; +use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; +use Tienvx\Bundle\MbtBundle\Entity\Task\Browser; use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; use Tienvx\Bundle\MbtBundle\Model\Task as TaskModel; use Tienvx\Bundle\MbtBundle\Model\Task\BrowserInterface; use Tienvx\Bundle\MbtBundle\Repository\TaskRepository; -/** - * @ORM\Entity(repositoryClass=TaskRepository::class) - * @ORM\HasLifecycleCallbacks - */ +#[ORM\Entity(repositoryClass: TaskRepository::class)] +#[ORM\HasLifecycleCallbacks] class Task extends TaskModel { - /** - * @ORM\Id - * @ORM\GeneratedValue - * @ORM\Column(type="integer") - */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: 'integer')] protected ?int $id = null; - /** - * @ORM\Column(type="string") - * @Assert\NotBlank - */ + #[ORM\Column(type: 'string')] + #[Assert\NotBlank] protected string $title = ''; - /** - * @ORM\ManyToOne(targetEntity="\Tienvx\Bundle\MbtBundle\Entity\Model\Revision") - * @ORM\JoinColumn(nullable=false) - * @Assert\Valid - * @Assert\NotNull - */ + #[ORM\ManyToOne(targetEntity: Revision::class)] + #[ORM\JoinColumn(nullable: false)] + #[Assert\Valid] + #[Assert\NotNull] protected RevisionInterface $modelRevision; - /** - * @ORM\Column(type="integer", nullable=true) - */ + #[ORM\Column(type: 'integer', nullable: true)] protected ?int $author = null; - /** - * @ORM\Column(type="boolean") - */ + #[ORM\Column(type: 'boolean')] protected bool $running = false; - /** - * @Embedded(class="\Tienvx\Bundle\MbtBundle\Entity\Task\Browser") - * @Assert\NotNull - */ + #[Embedded(class: Browser::class)] + #[Assert\NotNull] protected BrowserInterface $browser; - /** - * @ORM\OneToMany(targetEntity="\Tienvx\Bundle\MbtBundle\Entity\Bug", mappedBy="task", cascade={"persist"}) - * @ORM\JoinColumn(onDelete="CASCADE") - */ + #[ORM\OneToMany(targetEntity: Bug::class, mappedBy: 'task', cascade: ['persist'])] + #[ORM\JoinColumn(onDelete: 'CASCADE')] protected Collection $bugs; - /** - * @ORM\Column(type="boolean") - */ + #[ORM\Column(type: 'boolean')] protected bool $debug = false; - /** - * @ORM\Column(name="created_at", type="datetime") - */ + #[ORM\Column(name: 'created_at', type: 'datetime')] protected DateTimeInterface $createdAt; - /** - * @ORM\Column(name="updated_at", type="datetime") - */ + #[ORM\Column(name: 'updated_at', type: 'datetime')] protected DateTimeInterface $updatedAt; - /** - * @ORM\PrePersist - */ + #[ORM\PrePersist] public function prePersist(): void { $this->setCreatedAt(new DateTime()); $this->setUpdatedAt(new DateTime()); } - /** - * @ORM\PreUpdate - */ + #[ORM\PreUpdate] public function preUpdate(): void { $this->setUpdatedAt(new DateTime()); diff --git a/src/Entity/Task/Browser.php b/src/Entity/Task/Browser.php index 0ab5c490..db753c46 100644 --- a/src/Entity/Task/Browser.php +++ b/src/Entity/Task/Browser.php @@ -7,20 +7,14 @@ use Symfony\Component\Validator\Constraints as Assert; use Tienvx\Bundle\MbtBundle\Model\Task\Browser as BrowserModel; -/** - * @Embeddable - */ +#[Embeddable] class Browser extends BrowserModel { - /** - * @ORM\Column(type="string", nullable=false) - * @Assert\NotBlank - */ + #[ORM\Column(type: 'string', nullable: false)] + #[Assert\NotBlank] protected string $name = ''; - /** - * @ORM\Column(type="string", nullable=false) - * @Assert\NotBlank - */ + #[ORM\Column(type: 'string', nullable: false)] + #[Assert\NotBlank] protected string $version = ''; } diff --git a/src/Validator/Tags.php b/src/Validator/Tags.php index 973268b0..0995f09f 100644 --- a/src/Validator/Tags.php +++ b/src/Validator/Tags.php @@ -4,9 +4,7 @@ use Symfony\Component\Validator\Constraint; -/** - * @Annotation - */ +#[\Attribute(\Attribute::TARGET_PROPERTY)] class Tags extends Constraint { public const IS_TAGS_INVALID_ERROR = '628fca96-35f8-11eb-adc1-0242ac120002'; diff --git a/src/Validator/ValidCommand.php b/src/Validator/ValidCommand.php index 438014f7..47c5d0f6 100644 --- a/src/Validator/ValidCommand.php +++ b/src/Validator/ValidCommand.php @@ -4,9 +4,7 @@ use Symfony\Component\Validator\Constraint; -/** - * @Annotation - */ +#[\Attribute(\Attribute::TARGET_CLASS)] class ValidCommand extends Constraint { public const IS_COMMAND_INVALID_ERROR = 'ba5fd751-cbdf-45ab-a1e7-37045d5ef44b'; @@ -20,7 +18,7 @@ class ValidCommand extends Constraint public string $targetInvalidMessage = 'mbt.model.command.invalid_target'; public string $valueRequiredMessage = 'mbt.model.command.required_value'; - public function getTargets() + public function getTargets(): string|array { return self::CLASS_CONSTRAINT; } diff --git a/src/ValueObject/Bug/Step.php b/src/ValueObject/Bug/Step.php index cb89bca7..7da148b0 100644 --- a/src/ValueObject/Bug/Step.php +++ b/src/ValueObject/Bug/Step.php @@ -2,27 +2,22 @@ namespace Tienvx\Bundle\MbtBundle\ValueObject\Bug; +use SingleColorPetrinet\Model\Color; use SingleColorPetrinet\Model\ColorInterface; use Symfony\Component\Validator\Constraints as Assert; use Tienvx\Bundle\MbtBundle\Model\Bug\Step as StepModel; class Step extends StepModel { - /** - * @Assert\All({ - * @Assert\Type("integer") - * }) - * @Assert\Count(min=1, minMessage="mbt.bug.missing_places_in_step") - */ + #[Assert\All([ + new Assert\Type('integer'), + ])] + #[Assert\Count(min: 1, minMessage: 'mbt.bug.missing_places_in_step')] protected array $places; - /** - * @Assert\Type("\SingleColorPetrinet\Model\Color") - */ + #[Assert\Type(type: Color::class)] protected ColorInterface $color; - /** - * @Assert\Type("integer") - */ + #[Assert\Type('integer')] protected int $transition; } diff --git a/src/ValueObject/Model/Command.php b/src/ValueObject/Model/Command.php index 3b732872..315bb202 100644 --- a/src/ValueObject/Model/Command.php +++ b/src/ValueObject/Model/Command.php @@ -6,14 +6,10 @@ use Tienvx\Bundle\MbtBundle\Model\Model\Revision\Command as CommandModel; use Tienvx\Bundle\MbtBundle\Validator\ValidCommand; -/** - * @ValidCommand - */ +#[ValidCommand] class Command extends CommandModel { - /** - * @Assert\NotBlank - */ + #[Assert\NotBlank] protected string $command; protected ?string $target = null; diff --git a/src/ValueObject/Model/Place.php b/src/ValueObject/Model/Place.php index dddcafa2..f9062f57 100644 --- a/src/ValueObject/Model/Place.php +++ b/src/ValueObject/Model/Place.php @@ -7,16 +7,12 @@ class Place extends PlaceModel { - /** - * @Assert\NotBlank - */ + #[Assert\NotBlank] protected string $label = ''; - /** - * @Assert\All({ - * @Assert\Type("\Tienvx\Bundle\MbtBundle\ValueObject\Model\Command") - * }) - * @Assert\Valid - */ + #[Assert\All([ + new Assert\Type(type: Command::class), + ])] + #[Assert\Valid] protected array $commands = []; } diff --git a/src/ValueObject/Model/Transition.php b/src/ValueObject/Model/Transition.php index d6ea4399..694e376c 100644 --- a/src/ValueObject/Model/Transition.php +++ b/src/ValueObject/Model/Transition.php @@ -8,47 +8,35 @@ class Transition extends TransitionModel { - /** - * @Assert\NotBlank - */ + #[Assert\NotBlank] protected string $label = ''; - /** - * @Assert\AtLeastOneOf({ - * @Assert\IsNull, - * @Assert\ExpressionLanguageSyntax - * }) - */ + #[Assert\AtLeastOneOf([ + new Assert\IsNull(), + new Assert\ExpressionSyntax(), + ])] protected ?string $guard = null; - /** - * @Assert\AtLeastOneOf({ - * @Assert\IsNull, - * @AssignmentsSyntax - * }) - */ + #[Assert\AtLeastOneOf([ + new Assert\IsNull(), + new AssignmentsSyntax(), + ])] protected ?string $expression = null; - /** - * @Assert\All({ - * @Assert\Type("\Tienvx\Bundle\MbtBundle\ValueObject\Model\Command") - * }) - * @Assert\Valid - */ + #[Assert\All([ + new Assert\Type(Command::class), + ])] + #[Assert\Valid] protected array $commands = []; - /** - * @Assert\All({ - * @Assert\Type("integer") - * }) - */ + #[Assert\All([ + new Assert\Type('integer'), + ])] protected array $fromPlaces = []; - /** - * @Assert\All({ - * @Assert\Type("integer") - * }) - * @Assert\Count(min=1, minMessage="mbt.model.missing_to_places") - */ + #[Assert\All([ + new Assert\Type('integer'), + ])] + #[Assert\Count(min: 1, minMessage: 'mbt.model.missing_to_places')] protected array $toPlaces = []; } From 91fcc22b8168b225a48cd4ab2cb87b443fe00605 Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 15 Jul 2022 00:41:36 +0700 Subject: [PATCH 63/85] Remove Symfony 5.4 --- composer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 6cb3b81b..13c493bc 100644 --- a/composer.json +++ b/composer.json @@ -22,12 +22,12 @@ "doctrine/orm": "^2.8", "jmgq/a-star": "^2.1", "php-webdriver/webdriver": "^1.11", - "symfony/config": "^5.4|^6.1", - "symfony/dependency-injection": "^5.4|^6.1", - "symfony/expression-language": "^5.4|^6.1", - "symfony/http-kernel": "^5.4|^6.1", - "symfony/messenger": "^5.4|^6.1", - "symfony/validator": "^5.4|^6.1", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.1", + "symfony/expression-language": "^6.1", + "symfony/http-kernel": "^6.1", + "symfony/messenger": "^6.1", + "symfony/validator": "^6.1", "tienvx/single-color-petrinet": "^1.5", "tienvx/assignments-evaluator-bundle": "^1.0" }, From 0d1b5aa984428bf43e69fb617f8e1387ebb82f0f Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 15 Jul 2022 12:24:01 +0700 Subject: [PATCH 64/85] Fix unit tests failed with lowest assignments-evaluator-bundle version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 13c493bc..07c679d8 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "symfony/messenger": "^6.1", "symfony/validator": "^6.1", "tienvx/single-color-petrinet": "^1.5", - "tienvx/assignments-evaluator-bundle": "^1.0" + "tienvx/assignments-evaluator-bundle": "^1.0.1" }, "require-dev": { "phpunit/phpunit": "^9.5" From f126209737fe4b036103f42e8dc23bc532a6c137 Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 15 Jul 2022 12:35:57 +0700 Subject: [PATCH 65/85] Revert deleting new line --- src/Entity/Model/Revision.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Entity/Model/Revision.php b/src/Entity/Model/Revision.php index d0e9653b..02b9ed80 100644 --- a/src/Entity/Model/Revision.php +++ b/src/Entity/Model/Revision.php @@ -72,6 +72,7 @@ public function validateStartTransitions(ExecutionContextInterface $context, $pa if (0 === count($this->transitions)) { return; } + $startTransitions = array_filter( $this->transitions, fn ($transition) => $transition instanceof TransitionInterface && 0 === count($transition->getFromPlaces()) From 41e8328d1916aa8de0bd7e3cb93340bb8cc6323d Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 15 Jul 2022 12:36:41 +0700 Subject: [PATCH 66/85] Fix code coverage not uploaded --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9b2c5211..0097a5d5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -44,4 +44,4 @@ jobs: run: | composer global require php-coveralls/php-coveralls php-coveralls --coverage_clover=clover.xml -v - if: matrix.php-versions == '8.0' + if: matrix.php-versions == '8.1' From 18d7da66714e16cb6a6952a76987f4278e4c58cf Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 15 Jul 2022 13:00:55 +0700 Subject: [PATCH 67/85] Test upload command --- .../Runner/CustomCommandRunnerTest.php | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/Command/Runner/CustomCommandRunnerTest.php diff --git a/tests/Command/Runner/CustomCommandRunnerTest.php b/tests/Command/Runner/CustomCommandRunnerTest.php new file mode 100644 index 00000000..ba5d77d3 --- /dev/null +++ b/tests/Command/Runner/CustomCommandRunnerTest.php @@ -0,0 +1,57 @@ +setCommand(CustomCommandRunner::UPLOAD); + $command->setTarget('id=file_input'); + $command->setValue('sub-directory/file.txt'); + $element = $this->createMock(RemoteWebElement::class); + $element + ->expects($this->once()) + ->method('setFileDetector') + ->with($this->isInstanceOf(LocalFileDetector::class)) + ->willReturnSelf(); + $element + ->expects($this->once()) + ->method('sendKeys') + ->with('/path/to/upload-directory/sub-directory/file.txt'); + $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'file_input' === $selector->getValue(); + }))->willReturn($element); + $this->runner->run($command, $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [CustomCommandRunner::UPLOAD, null, false], + [CustomCommandRunner::UPLOAD, 'anything', false], + [CustomCommandRunner::UPLOAD, 'xpath=//path/to/element', true], + ]; + } +} From 47e45b3012fba968aa7811cc5a8e44595581e4d7 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 16 Jul 2022 00:07:53 +0700 Subject: [PATCH 68/85] Test commands require target and value --- .../Command/Runner/AlertCommandRunnerTest.php | 12 ++++++ tests/Command/Runner/AssertionRunnerTest.php | 38 +++++++++++++++++++ .../Runner/CustomCommandRunnerTest.php | 14 +++++++ .../Runner/KeyboardCommandRunnerTest.php | 13 +++++++ .../Command/Runner/MouseCommandRunnerTest.php | 38 +++++++++++++++++++ tests/Command/Runner/RunnerTestCase.php | 14 +++++++ .../Runner/ScriptCommandRunnerTest.php | 17 +++++++++ .../Command/Runner/StoreCommandRunnerTest.php | 26 +++++++++++++ .../Command/Runner/WaitCommandRunnerTest.php | 24 ++++++++++++ .../Runner/WindowCommandRunnerTest.php | 15 ++++++++ 10 files changed, 211 insertions(+) diff --git a/tests/Command/Runner/AlertCommandRunnerTest.php b/tests/Command/Runner/AlertCommandRunnerTest.php index 78764c98..42de9f40 100644 --- a/tests/Command/Runner/AlertCommandRunnerTest.php +++ b/tests/Command/Runner/AlertCommandRunnerTest.php @@ -87,4 +87,16 @@ public function targetProvider(): array [AlertCommandRunner::ANSWER_PROMPT, 'anything', true], ]; } + + public function commandsRequireTarget(): array + { + return [ + AlertCommandRunner::ANSWER_PROMPT, + ]; + } + + public function commandsRequireValue(): array + { + return []; + } } diff --git a/tests/Command/Runner/AssertionRunnerTest.php b/tests/Command/Runner/AssertionRunnerTest.php index b2f4c522..ea5097c3 100644 --- a/tests/Command/Runner/AssertionRunnerTest.php +++ b/tests/Command/Runner/AssertionRunnerTest.php @@ -602,4 +602,42 @@ public function targetProvider(): array [AssertionRunner::ASSERT, 'anything', true], ]; } + + public function commandsRequireTarget(): array + { + return [ + AssertionRunner::ASSERT, + AssertionRunner::ASSERT_ALERT, + AssertionRunner::ASSERT_CONFIRMATION, + AssertionRunner::ASSERT_PROMPT, + AssertionRunner::ASSERT_TITLE, + AssertionRunner::ASSERT_TEXT, + AssertionRunner::ASSERT_NOT_TEXT, + AssertionRunner::ASSERT_VALUE, + AssertionRunner::ASSERT_EDITABLE, + AssertionRunner::ASSERT_NOT_EDITABLE, + AssertionRunner::ASSERT_ELEMENT_PRESENT, + AssertionRunner::ASSERT_ELEMENT_NOT_PRESENT, + AssertionRunner::ASSERT_CHECKED, + AssertionRunner::ASSERT_NOT_CHECKED, + AssertionRunner::ASSERT_SELECTED_VALUE, + AssertionRunner::ASSERT_NOT_SELECTED_VALUE, + AssertionRunner::ASSERT_SELECTED_LABEL, + AssertionRunner::ASSERT_NOT_SELECTED_LABEL, + ]; + } + + public function commandsRequireValue(): array + { + return [ + AssertionRunner::ASSERT, + AssertionRunner::ASSERT_TEXT, + AssertionRunner::ASSERT_NOT_TEXT, + AssertionRunner::ASSERT_VALUE, + AssertionRunner::ASSERT_SELECTED_VALUE, + AssertionRunner::ASSERT_NOT_SELECTED_VALUE, + AssertionRunner::ASSERT_SELECTED_LABEL, + AssertionRunner::ASSERT_NOT_SELECTED_LABEL, + ]; + } } diff --git a/tests/Command/Runner/CustomCommandRunnerTest.php b/tests/Command/Runner/CustomCommandRunnerTest.php index ba5d77d3..eb5e9ea9 100644 --- a/tests/Command/Runner/CustomCommandRunnerTest.php +++ b/tests/Command/Runner/CustomCommandRunnerTest.php @@ -54,4 +54,18 @@ public function targetProvider(): array [CustomCommandRunner::UPLOAD, 'xpath=//path/to/element', true], ]; } + + public function commandsRequireTarget(): array + { + return [ + CustomCommandRunner::UPLOAD, + ]; + } + + public function commandsRequireValue(): array + { + return [ + CustomCommandRunner::UPLOAD, + ]; + } } diff --git a/tests/Command/Runner/KeyboardCommandRunnerTest.php b/tests/Command/Runner/KeyboardCommandRunnerTest.php index a475ca28..276d2719 100644 --- a/tests/Command/Runner/KeyboardCommandRunnerTest.php +++ b/tests/Command/Runner/KeyboardCommandRunnerTest.php @@ -64,4 +64,17 @@ public function targetProvider(): array [KeyboardCommandRunner::SEND_KEYS, 'xpath=//path/to/element', true], ]; } + + public function commandsRequireTarget(): array + { + return [ + KeyboardCommandRunner::TYPE, + KeyboardCommandRunner::SEND_KEYS, + ]; + } + + public function commandsRequireValue(): array + { + return []; + } } diff --git a/tests/Command/Runner/MouseCommandRunnerTest.php b/tests/Command/Runner/MouseCommandRunnerTest.php index f9762668..2b42ec12 100644 --- a/tests/Command/Runner/MouseCommandRunnerTest.php +++ b/tests/Command/Runner/MouseCommandRunnerTest.php @@ -575,4 +575,42 @@ public function targetProvider(): array [MouseCommandRunner::CLICK, 'xpath=//path/to/element', true], ]; } + + public function commandsRequireTarget(): array + { + return [ + MouseCommandRunner::ADD_SELECTION, + MouseCommandRunner::REMOVE_SELECTION, + MouseCommandRunner::CHECK, + MouseCommandRunner::UNCHECK, + MouseCommandRunner::CLICK, + MouseCommandRunner::CLICK_AT, + MouseCommandRunner::DOUBLE_CLICK, + MouseCommandRunner::DOUBLE_CLICK_AT, + MouseCommandRunner::DRAG_AND_DROP_TO_OBJECT, + MouseCommandRunner::MOUSE_DOWN, + MouseCommandRunner::MOUSE_DOWN_AT, + MouseCommandRunner::MOUSE_MOVE_AT, + MouseCommandRunner::MOUSE_OUT, + MouseCommandRunner::MOUSE_OVER, + MouseCommandRunner::MOUSE_UP, + MouseCommandRunner::MOUSE_UP_AT, + MouseCommandRunner::SELECT, + ]; + } + + public function commandsRequireValue(): array + { + return [ + MouseCommandRunner::ADD_SELECTION, + MouseCommandRunner::REMOVE_SELECTION, + MouseCommandRunner::CLICK_AT, + MouseCommandRunner::DOUBLE_CLICK_AT, + MouseCommandRunner::DRAG_AND_DROP_TO_OBJECT, + MouseCommandRunner::MOUSE_DOWN_AT, + MouseCommandRunner::MOUSE_MOVE_AT, + MouseCommandRunner::MOUSE_UP_AT, + MouseCommandRunner::SELECT, + ]; + } } diff --git a/tests/Command/Runner/RunnerTestCase.php b/tests/Command/Runner/RunnerTestCase.php index be6604f3..54565606 100644 --- a/tests/Command/Runner/RunnerTestCase.php +++ b/tests/Command/Runner/RunnerTestCase.php @@ -53,4 +53,18 @@ public function testValidateTarget(string $commandString, $target, bool $valid): } abstract public function targetProvider(): array; + + public function testGetCommandsRequireTarget(): void + { + $this->assertSame($this->commandsRequireTarget(), $this->runner->getCommandsRequireTarget()); + } + + abstract public function commandsRequireTarget(): array; + + public function testGetCommandsRequireValue(): void + { + $this->assertSame($this->commandsRequireValue(), $this->runner->getCommandsRequireValue()); + } + + abstract public function commandsRequireValue(): array; } diff --git a/tests/Command/Runner/ScriptCommandRunnerTest.php b/tests/Command/Runner/ScriptCommandRunnerTest.php index b8cd47ef..cdfa8625 100644 --- a/tests/Command/Runner/ScriptCommandRunnerTest.php +++ b/tests/Command/Runner/ScriptCommandRunnerTest.php @@ -68,4 +68,21 @@ public function targetProvider(): array [ScriptCommandRunner::RUN_SCRIPT, 'anything', true], ]; } + + public function commandsRequireTarget(): array + { + return [ + ScriptCommandRunner::RUN_SCRIPT, + ScriptCommandRunner::EXECUTE_SCRIPT, + ScriptCommandRunner::EXECUTE_ASYNC_SCRIPT, + ]; + } + + public function commandsRequireValue(): array + { + return [ + ScriptCommandRunner::EXECUTE_SCRIPT, + ScriptCommandRunner::EXECUTE_ASYNC_SCRIPT, + ]; + } } diff --git a/tests/Command/Runner/StoreCommandRunnerTest.php b/tests/Command/Runner/StoreCommandRunnerTest.php index 0ad2034a..6e4f914a 100644 --- a/tests/Command/Runner/StoreCommandRunnerTest.php +++ b/tests/Command/Runner/StoreCommandRunnerTest.php @@ -145,4 +145,30 @@ public function targetProvider(): array [StoreCommandRunner::STORE_JSON, '{"key": "value"}', true], ]; } + + public function commandsRequireTarget(): array + { + return [ + StoreCommandRunner::STORE_ATTRIBUTE, + StoreCommandRunner::STORE_ELEMENT_COUNT, + StoreCommandRunner::STORE_JSON, + StoreCommandRunner::STORE_TEXT, + StoreCommandRunner::STORE_TITLE, + StoreCommandRunner::STORE_VALUE, + StoreCommandRunner::STORE_WINDOW_HANDLE, + ]; + } + + public function commandsRequireValue(): array + { + return [ + StoreCommandRunner::STORE, + StoreCommandRunner::STORE_ATTRIBUTE, + StoreCommandRunner::STORE_ELEMENT_COUNT, + StoreCommandRunner::STORE_JSON, + StoreCommandRunner::STORE_TEXT, + StoreCommandRunner::STORE_TITLE, + StoreCommandRunner::STORE_VALUE, + ]; + } } diff --git a/tests/Command/Runner/WaitCommandRunnerTest.php b/tests/Command/Runner/WaitCommandRunnerTest.php index bed3b764..2f6d9e32 100644 --- a/tests/Command/Runner/WaitCommandRunnerTest.php +++ b/tests/Command/Runner/WaitCommandRunnerTest.php @@ -185,4 +185,28 @@ public function targetProvider(): array [WaitCommandRunner::WAIT_FOR_ELEMENT_VISIBLE, 'xpath=//path/to/element', true], ]; } + + public function commandsRequireTarget(): array + { + return [ + WaitCommandRunner::WAIT_FOR_ELEMENT_EDITABLE, + WaitCommandRunner::WAIT_FOR_ELEMENT_NOT_EDITABLE, + WaitCommandRunner::WAIT_FOR_ELEMENT_PRESENT, + WaitCommandRunner::WAIT_FOR_ELEMENT_NOT_PRESENT, + WaitCommandRunner::WAIT_FOR_ELEMENT_VISIBLE, + WaitCommandRunner::WAIT_FOR_ELEMENT_NOT_VISIBLE, + ]; + } + + public function commandsRequireValue(): array + { + return [ + WaitCommandRunner::WAIT_FOR_ELEMENT_EDITABLE, + WaitCommandRunner::WAIT_FOR_ELEMENT_NOT_EDITABLE, + WaitCommandRunner::WAIT_FOR_ELEMENT_PRESENT, + WaitCommandRunner::WAIT_FOR_ELEMENT_NOT_PRESENT, + WaitCommandRunner::WAIT_FOR_ELEMENT_VISIBLE, + WaitCommandRunner::WAIT_FOR_ELEMENT_NOT_VISIBLE, + ]; + } } diff --git a/tests/Command/Runner/WindowCommandRunnerTest.php b/tests/Command/Runner/WindowCommandRunnerTest.php index ba24e40b..dfbf5ff1 100644 --- a/tests/Command/Runner/WindowCommandRunnerTest.php +++ b/tests/Command/Runner/WindowCommandRunnerTest.php @@ -148,4 +148,19 @@ public function targetProvider(): array [WindowCommandRunner::SELECT_FRAME, 'xpath=//path/to/element', true], ]; } + + public function commandsRequireTarget(): array + { + return [ + WindowCommandRunner::OPEN, + WindowCommandRunner::SET_WINDOW_SIZE, + WindowCommandRunner::SELECT_WINDOW, + WindowCommandRunner::SELECT_FRAME, + ]; + } + + public function commandsRequireValue(): array + { + return []; + } } From 3c8921755aea0d9279bdb56aa85f049b8cb09a19 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 17 Jul 2022 17:54:22 +0700 Subject: [PATCH 69/85] Assert file downloaded --- composer.json | 1 + src/Command/Runner/CustomCommandRunner.php | 47 +++++++++- src/DependencyInjection/Configuration.php | 10 +++ .../TienvxMbtExtension.php | 6 ++ src/Resources/config/services.php | 4 + .../Runner/CustomCommandRunnerTest.php | 86 +++++++++++++++++-- tests/Command/Runner/RunnerTestCase.php | 8 +- .../TienvxMbtExtensionTest.php | 6 ++ .../Exception/HttpClientException.php | 9 ++ 9 files changed, 165 insertions(+), 12 deletions(-) create mode 100644 tests/Fixtures/Exception/HttpClientException.php diff --git a/composer.json b/composer.json index 07c679d8..abc37f53 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "symfony/config": "^6.1", "symfony/dependency-injection": "^6.1", "symfony/expression-language": "^6.1", + "symfony/http-client-contracts": "^3.1", "symfony/http-kernel": "^6.1", "symfony/messenger": "^6.1", "symfony/validator": "^6.1", diff --git a/src/Command/Runner/CustomCommandRunner.php b/src/Command/Runner/CustomCommandRunner.php index aa9d9395..9cf7d2e0 100644 --- a/src/Command/Runner/CustomCommandRunner.php +++ b/src/Command/Runner/CustomCommandRunner.php @@ -2,24 +2,43 @@ namespace Tienvx\Bundle\MbtBundle\Command\Runner; +use Exception; use Facebook\WebDriver\Remote\LocalFileDetector; use Facebook\WebDriver\Remote\RemoteWebDriver; +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; use Tienvx\Bundle\MbtBundle\Command\CommandRunner; +use Tienvx\Bundle\MbtBundle\Exception\RuntimeException; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; class CustomCommandRunner extends CommandRunner { public const UPLOAD = 'upload'; + public const ASSERT_FILE_DOWNLOADED = 'assertFileDownloaded'; - public function __construct(protected string $uploadDir) + protected string $uploadDir; + protected string $webdriverUri; + + public function __construct(protected HttpClientInterface $httpClient) + { + } + + public function setUploadDir(string $uploadDir): void + { + $this->uploadDir = $uploadDir; + } + + public function setWebdriverUri(string $webdriverUri): void { + $this->webdriverUri = $webdriverUri; } public function getAllCommands(): array { return [ self::UPLOAD, + self::ASSERT_FILE_DOWNLOADED, ]; } @@ -30,7 +49,9 @@ public function getCommandsRequireTarget(): array public function getCommandsRequireValue(): array { - return $this->getAllCommands(); + return [ + self::UPLOAD, + ]; } public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void @@ -42,6 +63,28 @@ public function run(CommandInterface $command, ValuesInterface $values, RemoteWe ->setFileDetector(new LocalFileDetector()) ->sendKeys($this->getFilePath($command)); break; + case self::ASSERT_FILE_DOWNLOADED: + try { + $code = $this->httpClient->request( + 'GET', + sprintf( + '%s/download/%s/%s', + rtrim($this->webdriverUri, '/'), + $driver->getSessionID(), + $command->getTarget() + ) + )->getStatusCode(); + if (200 !== $code) { + throw new Exception(sprintf('File %s is not downloaded', $command->getTarget())); + } + } catch (ExceptionInterface $e) { + throw new RuntimeException(sprintf( + 'Can not verify file %s is downloaded: %s', + $command->getTarget(), + $e->getMessage() + )); + } + break; default: break; } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index a97df678..adcebcde 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -8,6 +8,7 @@ class Configuration implements ConfigurationInterface { public const WEBDRIVER_URI = 'webdriver_uri'; + public const UPLOAD_DIR = 'upload_dir'; public function getConfigTreeBuilder(): TreeBuilder { @@ -24,6 +25,15 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ; + $rootNode + ->children() + ->scalarNode(static::UPLOAD_DIR) + ->isRequired() + ->cannotBeEmpty() + ->end() + ->end() + ; + return $treeBuilder; } } diff --git a/src/DependencyInjection/TienvxMbtExtension.php b/src/DependencyInjection/TienvxMbtExtension.php index 6652d75e..5c28fb7e 100644 --- a/src/DependencyInjection/TienvxMbtExtension.php +++ b/src/DependencyInjection/TienvxMbtExtension.php @@ -8,6 +8,7 @@ use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Tienvx\Bundle\MbtBundle\Command\CommandRunnerInterface; +use Tienvx\Bundle\MbtBundle\Command\Runner\CustomCommandRunner; use Tienvx\Bundle\MbtBundle\Plugin\PluginInterface; use Tienvx\Bundle\MbtBundle\Service\SelenoidHelperInterface; @@ -35,6 +36,11 @@ public function load(array $configs, ContainerBuilder $container): void ->addMethodCall('setWebdriverUri', [$config[Configuration::WEBDRIVER_URI]]) ; + $container->findDefinition(CustomCommandRunner::class) + ->addMethodCall('setWebdriverUri', [$config[Configuration::WEBDRIVER_URI]]) + ->addMethodCall('setUploadDir', [$config[Configuration::UPLOAD_DIR]]) + ; + $this->registerForAutoconfiguration($container); } diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index 906fbb9a..0e57772a 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -9,6 +9,7 @@ use SingleColorPetrinet\Service\GuardedTransitionService; use SingleColorPetrinet\Service\GuardedTransitionServiceInterface; use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; use Tienvx\AssignmentsEvaluator\AssignmentsEvaluator; use Tienvx\Bundle\MbtBundle\Channel\ChannelManager; use Tienvx\Bundle\MbtBundle\Channel\ChannelManagerInterface; @@ -199,6 +200,9 @@ ->autoconfigure(true) ->set(CustomCommandRunner::class) ->autoconfigure(true) + ->args([ + service(HttpClientInterface::class), + ]) // Repositories ->set(BugRepository::class) diff --git a/tests/Command/Runner/CustomCommandRunnerTest.php b/tests/Command/Runner/CustomCommandRunnerTest.php index eb5e9ea9..fa16d7d2 100644 --- a/tests/Command/Runner/CustomCommandRunnerTest.php +++ b/tests/Command/Runner/CustomCommandRunnerTest.php @@ -2,11 +2,16 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Command\Runner; +use Exception; use Facebook\WebDriver\Remote\LocalFileDetector; use Facebook\WebDriver\Remote\RemoteWebElement; use Facebook\WebDriver\WebDriverBy; -use Tienvx\Bundle\MbtBundle\Command\CommandRunner; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; use Tienvx\Bundle\MbtBundle\Command\Runner\CustomCommandRunner; +use Tienvx\Bundle\MbtBundle\Exception\RuntimeException; +use Tienvx\Bundle\MbtBundle\Tests\Fixtures\Exception\HttpClientException; use Tienvx\Bundle\MbtBundle\ValueObject\Model\Command; /** @@ -17,9 +22,19 @@ */ class CustomCommandRunnerTest extends RunnerTestCase { - protected function createRunner(): CommandRunner + protected string $webdriverUri = 'http://localhost:4444'; + protected string $uploadDir = '/path/to/upload-directory'; + protected string $sessionId = 'abc123'; + protected HttpClientInterface|MockObject $httpClient; + + protected function createRunner(): CustomCommandRunner { - return new CustomCommandRunner('/path/to/upload-directory'); + $this->httpClient = $this->createMock(HttpClientInterface::class); + $runner = new CustomCommandRunner($this->httpClient); + $runner->setWebdriverUri($this->webdriverUri); + $runner->setUploadDir($this->uploadDir); + + return $runner; } public function testUpload(): void @@ -28,21 +43,75 @@ public function testUpload(): void $command->setCommand(CustomCommandRunner::UPLOAD); $command->setTarget('id=file_input'); $command->setValue('sub-directory/file.txt'); - $element = $this->createMock(RemoteWebElement::class); - $element + $this->element = $this->createMock(RemoteWebElement::class); + $this->element ->expects($this->once()) ->method('setFileDetector') ->with($this->isInstanceOf(LocalFileDetector::class)) ->willReturnSelf(); - $element + $this->element ->expects($this->once()) ->method('sendKeys') - ->with('/path/to/upload-directory/sub-directory/file.txt'); + ->with($this->uploadDir . '/sub-directory/file.txt'); $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { return $selector instanceof WebDriverBy && 'id' === $selector->getMechanism() && 'file_input' === $selector->getValue(); - }))->willReturn($element); + }))->willReturn($this->element); + $this->runner->run($command, $this->values, $this->driver); + } + + /** + * @dataProvider statusCodeProvider + */ + public function testAssertFileDownloaded(int $code): void + { + $command = new Command(); + $command->setCommand(CustomCommandRunner::ASSERT_FILE_DOWNLOADED); + $command->setTarget('file.txt'); + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->once())->method('getStatusCode')->willReturn($code); + $this->driver->expects($this->once())->method('getSessionID')->willReturn($this->sessionId); + $this->httpClient + ->expects($this->once()) + ->method('request') + ->with( + 'GET', + $this->webdriverUri . '/download/' . $this->sessionId . '/file.txt' + ) + ->willReturn($response); + if (200 !== $code) { + $this->expectException(Exception::class); + $this->expectExceptionMessage('File file.txt is not downloaded'); + } + $this->runner->run($command, $this->values, $this->driver); + } + + public function statusCodeProvider(): array + { + return [ + [200], + [404], + ]; + } + + public function testAssertFileDownloadedThrowException(): void + { + $command = new Command(); + $command->setCommand(CustomCommandRunner::ASSERT_FILE_DOWNLOADED); + $command->setTarget('file.txt'); + $this->runner->setWebdriverUri($this->webdriverUri); + $this->driver->expects($this->once())->method('getSessionID')->willReturn($this->sessionId); + $this->httpClient + ->expects($this->once()) + ->method('request') + ->with( + 'GET', + $this->webdriverUri . '/download/' . $this->sessionId . '/file.txt' + ) + ->willThrowException(new HttpClientException('Something wrong')); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Can not verify file file.txt is downloaded: Something wrong'); $this->runner->run($command, $this->values, $this->driver); } @@ -59,6 +128,7 @@ public function commandsRequireTarget(): array { return [ CustomCommandRunner::UPLOAD, + CustomCommandRunner::ASSERT_FILE_DOWNLOADED, ]; } diff --git a/tests/Command/Runner/RunnerTestCase.php b/tests/Command/Runner/RunnerTestCase.php index 54565606..a88e1592 100644 --- a/tests/Command/Runner/RunnerTestCase.php +++ b/tests/Command/Runner/RunnerTestCase.php @@ -3,6 +3,8 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Command\Runner; use Facebook\WebDriver\Remote\RemoteWebDriver; +use Facebook\WebDriver\WebDriverElement; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Tienvx\Bundle\MbtBundle\Command\CommandRunner; use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; @@ -10,15 +12,17 @@ abstract class RunnerTestCase extends TestCase { - protected RemoteWebDriver $driver; + protected RemoteWebDriver|MockObject $driver; protected CommandRunner $runner; - protected ValuesInterface $values; + protected ValuesInterface|MockObject $values; + protected WebDriverElement|MockObject $element; protected function setUp(): void { $this->driver = $this->createMock(RemoteWebDriver::class); $this->runner = $this->createRunner(); $this->values = $this->createMock(ValuesInterface::class); + $this->element = $this->createMock(WebDriverElement::class); } abstract protected function createRunner(): CommandRunner; diff --git a/tests/DependencyInjection/TienvxMbtExtensionTest.php b/tests/DependencyInjection/TienvxMbtExtensionTest.php index 8403c8ca..884a7400 100644 --- a/tests/DependencyInjection/TienvxMbtExtensionTest.php +++ b/tests/DependencyInjection/TienvxMbtExtensionTest.php @@ -6,6 +6,7 @@ use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\ContainerBuilder; use Tienvx\Bundle\MbtBundle\Command\CommandRunnerInterface; +use Tienvx\Bundle\MbtBundle\Command\Runner\CustomCommandRunner; use Tienvx\Bundle\MbtBundle\DependencyInjection\Configuration; use Tienvx\Bundle\MbtBundle\DependencyInjection\TienvxMbtExtension; use Tienvx\Bundle\MbtBundle\Plugin\PluginInterface; @@ -19,6 +20,7 @@ class TienvxMbtExtensionTest extends TestCase { protected const CONFIGS = [[ Configuration::WEBDRIVER_URI => 'http://localhost:4444', + Configuration::UPLOAD_DIR => '/path/to/var/uploads', ]]; protected ContainerBuilder $container; @@ -43,6 +45,10 @@ public function testUpdateServiceDefinitions(): void $this->assertSame([ ['setWebdriverUri', [static::CONFIGS[0][Configuration::WEBDRIVER_URI]]], ], $this->container->findDefinition(SelenoidHelperInterface::class)->getMethodCalls()); + $this->assertSame([ + ['setWebdriverUri', [static::CONFIGS[0][Configuration::WEBDRIVER_URI]]], + ['setUploadDir', [static::CONFIGS[0][Configuration::UPLOAD_DIR]]], + ], $this->container->findDefinition(CustomCommandRunner::class)->getMethodCalls()); } public function testRegisterForAutoconfiguration(): void diff --git a/tests/Fixtures/Exception/HttpClientException.php b/tests/Fixtures/Exception/HttpClientException.php new file mode 100644 index 00000000..3894ed7a --- /dev/null +++ b/tests/Fixtures/Exception/HttpClientException.php @@ -0,0 +1,9 @@ + Date: Sun, 17 Jul 2022 21:17:22 +0700 Subject: [PATCH 70/85] Assert clipboard --- src/Command/Runner/CustomCommandRunner.php | 29 ++++++++- .../Runner/CustomCommandRunnerTest.php | 64 ++++++++++++++++++- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/src/Command/Runner/CustomCommandRunner.php b/src/Command/Runner/CustomCommandRunner.php index 9cf7d2e0..14525e9f 100644 --- a/src/Command/Runner/CustomCommandRunner.php +++ b/src/Command/Runner/CustomCommandRunner.php @@ -16,6 +16,7 @@ class CustomCommandRunner extends CommandRunner { public const UPLOAD = 'upload'; public const ASSERT_FILE_DOWNLOADED = 'assertFileDownloaded'; + public const ASSERT_CLIPBOARD = 'assertClipboard'; protected string $uploadDir; protected string $webdriverUri; @@ -39,6 +40,7 @@ public function getAllCommands(): array return [ self::UPLOAD, self::ASSERT_FILE_DOWNLOADED, + self::ASSERT_CLIPBOARD, ]; } @@ -75,16 +77,39 @@ public function run(CommandInterface $command, ValuesInterface $values, RemoteWe ) )->getStatusCode(); if (200 !== $code) { - throw new Exception(sprintf('File %s is not downloaded', $command->getTarget())); + throw new Exception(sprintf( + 'Failed expecting that file %s is downloaded', + $command->getTarget() + )); } } catch (ExceptionInterface $e) { throw new RuntimeException(sprintf( - 'Can not verify file %s is downloaded: %s', + 'Can not get downloaded file %s: %s', $command->getTarget(), $e->getMessage() )); } break; + case self::ASSERT_CLIPBOARD: + try { + $clipboard = $this->httpClient->request( + 'GET', + sprintf('%s/clipboard/%s', rtrim($this->webdriverUri, '/'), $driver->getSessionID()) + )->getContent(); + if ($command->getTarget() !== $clipboard) { + throw new Exception(sprintf( + "Failed expecting that clipboard's content equals '%s', actual value '%s'", + $command->getTarget(), + $clipboard + )); + } + } catch (ExceptionInterface $e) { + throw new RuntimeException(sprintf( + 'Can not get clipboard: %s', + $e->getMessage() + )); + } + break; default: break; } diff --git a/tests/Command/Runner/CustomCommandRunnerTest.php b/tests/Command/Runner/CustomCommandRunnerTest.php index fa16d7d2..a3fefcc9 100644 --- a/tests/Command/Runner/CustomCommandRunnerTest.php +++ b/tests/Command/Runner/CustomCommandRunnerTest.php @@ -25,6 +25,7 @@ class CustomCommandRunnerTest extends RunnerTestCase protected string $webdriverUri = 'http://localhost:4444'; protected string $uploadDir = '/path/to/upload-directory'; protected string $sessionId = 'abc123'; + protected string $clipboard = 'text 1'; protected HttpClientInterface|MockObject $httpClient; protected function createRunner(): CustomCommandRunner @@ -82,7 +83,7 @@ public function testAssertFileDownloaded(int $code): void ->willReturn($response); if (200 !== $code) { $this->expectException(Exception::class); - $this->expectExceptionMessage('File file.txt is not downloaded'); + $this->expectExceptionMessage('Failed expecting that file file.txt is downloaded'); } $this->runner->run($command, $this->values, $this->driver); } @@ -111,7 +112,65 @@ public function testAssertFileDownloadedThrowException(): void ) ->willThrowException(new HttpClientException('Something wrong')); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Can not verify file file.txt is downloaded: Something wrong'); + $this->expectExceptionMessage('Can not get downloaded file file.txt: Something wrong'); + $this->runner->run($command, $this->values, $this->driver); + } + + /** + * @dataProvider clipboardProvider + */ + public function testAssertClipbard(string $clipboard): void + { + $command = new Command(); + $command->setCommand(CustomCommandRunner::ASSERT_CLIPBOARD); + $command->setTarget($this->clipboard); + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->once())->method('getContent')->willReturn($clipboard); + $this->driver->expects($this->once())->method('getSessionID')->willReturn($this->sessionId); + $this->httpClient + ->expects($this->once()) + ->method('request') + ->with( + 'GET', + $this->webdriverUri . '/clipboard/' . $this->sessionId + ) + ->willReturn($response); + if ($clipboard !== $this->clipboard) { + $this->expectException(Exception::class); + $this->expectExceptionMessage(sprintf( + "Failed expecting that clipboard's content equals '%s', actual value '%s'", + $this->clipboard, + $clipboard + )); + } + $this->runner->run($command, $this->values, $this->driver); + } + + public function clipboardProvider(): array + { + return [ + [$this->clipboard], + ['text 2'], + ]; + } + + public function testAssertClipboardThrowException(): void + { + $command = new Command(); + $command->setCommand(CustomCommandRunner::ASSERT_CLIPBOARD); + $command->setTarget($this->clipboard); + $this->runner->setWebdriverUri($this->webdriverUri); + $this->driver->expects($this->once())->method('getSessionID')->willReturn($this->sessionId); + $this->httpClient + ->expects($this->once()) + ->method('request') + ->with( + 'GET', + $this->webdriverUri . '/clipboard/' . $this->sessionId + ) + ->willThrowException(new HttpClientException('Something wrong')); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Can not get clipboard: Something wrong'); $this->runner->run($command, $this->values, $this->driver); } @@ -129,6 +188,7 @@ public function commandsRequireTarget(): array return [ CustomCommandRunner::UPLOAD, CustomCommandRunner::ASSERT_FILE_DOWNLOADED, + CustomCommandRunner::ASSERT_CLIPBOARD, ]; } From 18b4aa861337b9d7ee7932798ddd68c98d5c3fd2 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 17 Jul 2022 22:10:26 +0700 Subject: [PATCH 71/85] Update clipboard --- src/Command/Runner/CustomCommandRunner.php | 33 ++++++++++----- .../Runner/CustomCommandRunnerTest.php | 42 +++++++++++++++++++ 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/src/Command/Runner/CustomCommandRunner.php b/src/Command/Runner/CustomCommandRunner.php index 14525e9f..5b45c39f 100644 --- a/src/Command/Runner/CustomCommandRunner.php +++ b/src/Command/Runner/CustomCommandRunner.php @@ -17,6 +17,7 @@ class CustomCommandRunner extends CommandRunner public const UPLOAD = 'upload'; public const ASSERT_FILE_DOWNLOADED = 'assertFileDownloaded'; public const ASSERT_CLIPBOARD = 'assertClipboard'; + public const UPDATE_CLIPBOARD = 'updateClipboard'; protected string $uploadDir; protected string $webdriverUri; @@ -41,6 +42,7 @@ public function getAllCommands(): array self::UPLOAD, self::ASSERT_FILE_DOWNLOADED, self::ASSERT_CLIPBOARD, + self::UPDATE_CLIPBOARD, ]; } @@ -69,12 +71,7 @@ public function run(CommandInterface $command, ValuesInterface $values, RemoteWe try { $code = $this->httpClient->request( 'GET', - sprintf( - '%s/download/%s/%s', - rtrim($this->webdriverUri, '/'), - $driver->getSessionID(), - $command->getTarget() - ) + sprintf('%s/%s', $this->getUrl('download', $driver), $command->getTarget()) )->getStatusCode(); if (200 !== $code) { throw new Exception(sprintf( @@ -92,10 +89,7 @@ public function run(CommandInterface $command, ValuesInterface $values, RemoteWe break; case self::ASSERT_CLIPBOARD: try { - $clipboard = $this->httpClient->request( - 'GET', - sprintf('%s/clipboard/%s', rtrim($this->webdriverUri, '/'), $driver->getSessionID()) - )->getContent(); + $clipboard = $this->httpClient->request('GET', $this->getUrl('clipboard', $driver))->getContent(); if ($command->getTarget() !== $clipboard) { throw new Exception(sprintf( "Failed expecting that clipboard's content equals '%s', actual value '%s'", @@ -110,6 +104,20 @@ public function run(CommandInterface $command, ValuesInterface $values, RemoteWe )); } break; + case self::UPDATE_CLIPBOARD: + try { + $this->httpClient->request( + 'POST', + $this->getUrl('clipboard', $driver), + ['body' => $command->getTarget()] + )->getStatusCode(); + } catch (ExceptionInterface $e) { + throw new RuntimeException(sprintf( + 'Can not update clipboard: %s', + $e->getMessage() + )); + } + break; default: break; } @@ -119,4 +127,9 @@ protected function getFilePath(CommandInterface $command): string { return $this->uploadDir . DIRECTORY_SEPARATOR . (string) $command->getValue(); } + + protected function getUrl(string $type, RemoteWebDriver $driver): string + { + return sprintf('%s/%s/%s', rtrim($this->webdriverUri, '/'), $type, $driver->getSessionID()); + } } diff --git a/tests/Command/Runner/CustomCommandRunnerTest.php b/tests/Command/Runner/CustomCommandRunnerTest.php index a3fefcc9..0bb2b7d0 100644 --- a/tests/Command/Runner/CustomCommandRunnerTest.php +++ b/tests/Command/Runner/CustomCommandRunnerTest.php @@ -174,6 +174,47 @@ public function testAssertClipboardThrowException(): void $this->runner->run($command, $this->values, $this->driver); } + public function testUpdateClipboard(): void + { + $command = new Command(); + $command->setCommand(CustomCommandRunner::UPDATE_CLIPBOARD); + $command->setTarget('clipboard'); + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->once())->method('getStatusCode')->willReturn(123); + $this->driver->expects($this->once())->method('getSessionID')->willReturn($this->sessionId); + $this->httpClient + ->expects($this->once()) + ->method('request') + ->with( + 'POST', + $this->webdriverUri . '/clipboard/' . $this->sessionId, + ['body' => 'clipboard'] + ) + ->willReturn($response); + $this->runner->run($command, $this->values, $this->driver); + } + + public function testUpdateClipboardThrowException(): void + { + $command = new Command(); + $command->setCommand(CustomCommandRunner::UPDATE_CLIPBOARD); + $command->setTarget('text'); + $this->runner->setWebdriverUri($this->webdriverUri); + $this->driver->expects($this->once())->method('getSessionID')->willReturn($this->sessionId); + $this->httpClient + ->expects($this->once()) + ->method('request') + ->with( + 'POST', + $this->webdriverUri . '/clipboard/' . $this->sessionId, + ['body' => 'text'] + ) + ->willThrowException(new HttpClientException('Something wrong')); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Can not update clipboard: Something wrong'); + $this->runner->run($command, $this->values, $this->driver); + } + public function targetProvider(): array { return [ @@ -189,6 +230,7 @@ public function commandsRequireTarget(): array CustomCommandRunner::UPLOAD, CustomCommandRunner::ASSERT_FILE_DOWNLOADED, CustomCommandRunner::ASSERT_CLIPBOARD, + CustomCommandRunner::UPDATE_CLIPBOARD, ]; } From e06cebd08966be714a92a8a726b812e22d5aaf41 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 19 Jul 2022 00:33:25 +0700 Subject: [PATCH 72/85] Use new bundle structure. Use new attributes from Symfony 6.1 --- config/definition.php | 22 +++++++ {src/Resources/config => config}/services.php | 0 src/Command/CommandRunnerInterface.php | 2 + src/DependencyInjection/Configuration.php | 39 ----------- .../TienvxMbtExtension.php | 56 ---------------- src/Plugin/PluginInterface.php | 3 + src/TienvxMbtBundle.php | 31 ++++++++- .../TienvxMbtExtensionTest.php | 64 ------------------- tests/TienvxMbtBundleTest.php | 59 +++++++++++++++-- .../validators.en.php | 0 10 files changed, 110 insertions(+), 166 deletions(-) create mode 100644 config/definition.php rename {src/Resources/config => config}/services.php (100%) delete mode 100644 src/DependencyInjection/Configuration.php delete mode 100644 src/DependencyInjection/TienvxMbtExtension.php delete mode 100644 tests/DependencyInjection/TienvxMbtExtensionTest.php rename {src/Resources/translations => translations}/validators.en.php (100%) diff --git a/config/definition.php b/config/definition.php new file mode 100644 index 00000000..1653d03b --- /dev/null +++ b/config/definition.php @@ -0,0 +1,22 @@ +rootNode() + ->children() + ->scalarNode(TienvxMbtBundle::WEBDRIVER_URI) + ->isRequired() + ->cannotBeEmpty() + ->defaultValue('http://localhost:4444') + ->end() + ->end() + ->children() + ->scalarNode(TienvxMbtBundle::UPLOAD_DIR) + ->isRequired() + ->cannotBeEmpty() + ->end() + ->end() + ; +}; diff --git a/src/Resources/config/services.php b/config/services.php similarity index 100% rename from src/Resources/config/services.php rename to config/services.php diff --git a/src/Command/CommandRunnerInterface.php b/src/Command/CommandRunnerInterface.php index fc943033..23b2d696 100644 --- a/src/Command/CommandRunnerInterface.php +++ b/src/Command/CommandRunnerInterface.php @@ -3,9 +3,11 @@ namespace Tienvx\Bundle\MbtBundle\Command; use Facebook\WebDriver\Remote\RemoteWebDriver; +use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; +#[Autoconfigure(tags: [CommandRunnerInterface::TAG], lazy: true)] interface CommandRunnerInterface { public const MECHANISM_ID = 'id'; diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php deleted file mode 100644 index adcebcde..00000000 --- a/src/DependencyInjection/Configuration.php +++ /dev/null @@ -1,39 +0,0 @@ -getRootNode(); - - $rootNode - ->children() - ->scalarNode(static::WEBDRIVER_URI) - ->isRequired() - ->cannotBeEmpty() - ->defaultValue('http://localhost:4444') - ->end() - ->end() - ; - - $rootNode - ->children() - ->scalarNode(static::UPLOAD_DIR) - ->isRequired() - ->cannotBeEmpty() - ->end() - ->end() - ; - - return $treeBuilder; - } -} diff --git a/src/DependencyInjection/TienvxMbtExtension.php b/src/DependencyInjection/TienvxMbtExtension.php deleted file mode 100644 index 5c28fb7e..00000000 --- a/src/DependencyInjection/TienvxMbtExtension.php +++ /dev/null @@ -1,56 +0,0 @@ -load('services.php'); - - $configuration = new Configuration(); - $config = $this->processConfiguration($configuration, $configs); - - $container->findDefinition(SelenoidHelperInterface::class) - ->addMethodCall('setWebdriverUri', [$config[Configuration::WEBDRIVER_URI]]) - ; - - $container->findDefinition(CustomCommandRunner::class) - ->addMethodCall('setWebdriverUri', [$config[Configuration::WEBDRIVER_URI]]) - ->addMethodCall('setUploadDir', [$config[Configuration::UPLOAD_DIR]]) - ; - - $this->registerForAutoconfiguration($container); - } - - private function registerForAutoconfiguration(ContainerBuilder $container): void - { - $container->registerForAutoconfiguration(PluginInterface::class) - ->setLazy(true) - ->addTag(PluginInterface::TAG); - $container->registerForAutoconfiguration(CommandRunnerInterface::class) - ->setLazy(true) - ->addTag(CommandRunnerInterface::TAG); - } -} diff --git a/src/Plugin/PluginInterface.php b/src/Plugin/PluginInterface.php index 46863a4b..f90dd3fe 100644 --- a/src/Plugin/PluginInterface.php +++ b/src/Plugin/PluginInterface.php @@ -2,6 +2,9 @@ namespace Tienvx\Bundle\MbtBundle\Plugin; +use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; + +#[Autoconfigure(tags: [PluginInterface::TAG], lazy: true)] interface PluginInterface { public const TAG = 'mbt_bundle.plugin'; diff --git a/src/TienvxMbtBundle.php b/src/TienvxMbtBundle.php index dc8f63e6..37c4cf5b 100644 --- a/src/TienvxMbtBundle.php +++ b/src/TienvxMbtBundle.php @@ -2,14 +2,41 @@ namespace Tienvx\Bundle\MbtBundle; +use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\HttpKernel\Bundle\Bundle; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use Symfony\Component\HttpKernel\Bundle\AbstractBundle; +use Tienvx\Bundle\MbtBundle\Command\Runner\CustomCommandRunner; use Tienvx\Bundle\MbtBundle\DependencyInjection\Compiler\PluginPass; +use Tienvx\Bundle\MbtBundle\Service\SelenoidHelperInterface; -class TienvxMbtBundle extends Bundle +class TienvxMbtBundle extends AbstractBundle { + public const WEBDRIVER_URI = 'webdriver_uri'; + public const UPLOAD_DIR = 'upload_dir'; + public function build(ContainerBuilder $container): void { $container->addCompilerPass(new PluginPass()); } + + public function configure(DefinitionConfigurator $definition): void + { + $definition->import('../config/definition.php'); + } + + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void + { + $container->import('../config/services.php'); + + $container->services() + ->get(SelenoidHelperInterface::class) + ->call('setWebdriverUri', [$config[static::WEBDRIVER_URI]]) + ; + $container->services() + ->get(CustomCommandRunner::class) + ->call('setWebdriverUri', [$config[static::WEBDRIVER_URI]]) + ->call('setUploadDir', [$config[static::UPLOAD_DIR]]) + ; + } } diff --git a/tests/DependencyInjection/TienvxMbtExtensionTest.php b/tests/DependencyInjection/TienvxMbtExtensionTest.php deleted file mode 100644 index 884a7400..00000000 --- a/tests/DependencyInjection/TienvxMbtExtensionTest.php +++ /dev/null @@ -1,64 +0,0 @@ - 'http://localhost:4444', - Configuration::UPLOAD_DIR => '/path/to/var/uploads', - ]]; - - protected ContainerBuilder $container; - protected TienvxMbtExtension $loader; - - protected function setUp(): void - { - $this->container = new ContainerBuilder(); - $this->loader = new TienvxMbtExtension(); - } - - public function testExceptionMissingSeleniumServer(): void - { - $this->expectException(InvalidConfigurationException::class); - $loader = new TienvxMbtExtension(); - $loader->load([], new ContainerBuilder()); - } - - public function testUpdateServiceDefinitions(): void - { - $this->loader->load(static::CONFIGS, $this->container); - $this->assertSame([ - ['setWebdriverUri', [static::CONFIGS[0][Configuration::WEBDRIVER_URI]]], - ], $this->container->findDefinition(SelenoidHelperInterface::class)->getMethodCalls()); - $this->assertSame([ - ['setWebdriverUri', [static::CONFIGS[0][Configuration::WEBDRIVER_URI]]], - ['setUploadDir', [static::CONFIGS[0][Configuration::UPLOAD_DIR]]], - ], $this->container->findDefinition(CustomCommandRunner::class)->getMethodCalls()); - } - - public function testRegisterForAutoconfiguration(): void - { - $this->loader->load(static::CONFIGS, $this->container); - $interfaces = [PluginInterface::class, CommandRunnerInterface::class]; - foreach ($interfaces as $interface) { - $autoConfigured = $this->container->getAutoconfiguredInstanceof()[$interface]; - $this->assertTrue($autoConfigured->hasTag($interface::TAG)); - $this->assertTrue($autoConfigured->isLazy()); - } - } -} diff --git a/tests/TienvxMbtBundleTest.php b/tests/TienvxMbtBundleTest.php index 453c4cae..500c4518 100644 --- a/tests/TienvxMbtBundleTest.php +++ b/tests/TienvxMbtBundleTest.php @@ -2,8 +2,13 @@ namespace Tienvx\Bundle\MbtBundle\Tests; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +// use Symfony\Component\DependencyInjection\Loader\Configurator\ServiceConfigurator; +// use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator; use Tienvx\Bundle\MbtBundle\DependencyInjection\Compiler\PluginPass; use Tienvx\Bundle\MbtBundle\TienvxMbtBundle; @@ -12,13 +17,29 @@ */ class TienvxMbtBundleTest extends TestCase { + protected const CONFIG = [ + TienvxMbtBundle::WEBDRIVER_URI => 'http://localhost:4444', + TienvxMbtBundle::UPLOAD_DIR => '/path/to/var/uploads', + ]; + + protected ContainerBuilder $builder; + // protected ContainerConfigurator|MockObject $container; + protected DefinitionConfigurator|MockObject $definition; + protected TienvxMbtBundle $bundle; + + protected function setUp(): void + { + $this->builder = new ContainerBuilder(); + // $this->container = $this->createMock(ContainerConfigurator::class); + $this->definition = $this->createMock(DefinitionConfigurator::class); + $this->bundle = new TienvxMbtBundle(); + } + public function testBuild(): void { - $container = new ContainerBuilder(); - $oldPasses = $container->getCompiler()->getPassConfig()->getBeforeOptimizationPasses(); - $bundle = new TienvxMbtBundle(); - $bundle->build($container); - $newPasses = $container->getCompiler()->getPassConfig()->getBeforeOptimizationPasses(); + $oldPasses = $this->builder->getCompiler()->getPassConfig()->getBeforeOptimizationPasses(); + $this->bundle->build($this->builder); + $newPasses = $this->builder->getCompiler()->getPassConfig()->getBeforeOptimizationPasses(); $this->assertSame(1, count($newPasses) - count($oldPasses)); $hasPass = false; foreach ($newPasses as $pass) { @@ -28,4 +49,32 @@ public function testBuild(): void } $this->assertTrue($hasPass); } + + public function testConfigure(): void + { + $this->definition->expects($this->once())->method('import')->with('../config/definition.php'); + $this->bundle->configure($this->definition); + } + + /*public function testLoadExtension(): void + { + $this->container->expects($this->once())->method('import')->with('../config/services.php'); + $services = $this->createMock(ServicesConfigurator::class); + $selenoidHelper = $this->createMock(ServiceConfigurator::class); + $customCommandRunner = $this->createMock(ServiceConfigurator::class); + $services->expects($this->exactly(2))->method('get')->willReturnMap([ + [SelenoidHelperInterface::class, $selenoidHelper], + [CustomCommandRunner::class, $customCommandRunner], + ]); + $selenoidHelper + ->expects($this->once()) + ->method('call') + ->with('setWebdriverUri', [static::CONFIG[TienvxMbtBundle::WEBDRIVER_URI]]); + $customCommandRunner->expects($this->exactly(2))->method('call')->withConsecutive([ + ['setWebdriverUri', [static::CONFIG[TienvxMbtBundle::WEBDRIVER_URI]]], + ['setUploadDir', [static::CONFIG[TienvxMbtBundle::UPLOAD_DIR]]], + ]); + $this->container->expects($this->exactly(2))->method('services')->willReturn($services); + $this->bundle->loadExtension(static::CONFIG, $this->container, $this->builder); + }*/ } diff --git a/src/Resources/translations/validators.en.php b/translations/validators.en.php similarity index 100% rename from src/Resources/translations/validators.en.php rename to translations/validators.en.php From bd958ee3c75094c0fecca22fd58dc3165f266804 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 19 Jul 2022 21:04:06 +0700 Subject: [PATCH 73/85] Remove un-used variable --- src/DependencyInjection/Compiler/PluginPass.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DependencyInjection/Compiler/PluginPass.php b/src/DependencyInjection/Compiler/PluginPass.php index 5cce8f89..22db1de3 100644 --- a/src/DependencyInjection/Compiler/PluginPass.php +++ b/src/DependencyInjection/Compiler/PluginPass.php @@ -14,7 +14,7 @@ class PluginPass implements CompilerPassInterface public function process(ContainerBuilder $container): void { $allPlugins = []; - foreach ($container->findTaggedServiceIds(PluginInterface::TAG, true) as $serviceId => $attributes) { + foreach (array_keys($container->findTaggedServiceIds(PluginInterface::TAG, true)) as $serviceId) { $definition = $container->getDefinition($serviceId); $class = $definition->getClass(); if (is_subclass_of($class, PluginInterface::class) && $class::isSupported()) { From c23190b8fe86bdbe7f23c1f53907459168f48dc6 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 19 Jul 2022 21:19:05 +0700 Subject: [PATCH 74/85] Update changelog and readme --- CHANGELOG.md | 9 +++++++++ README.md | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9fa43e1..8b350899 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ CHANGELOG ========= +3.0.0 +----- + +* Only support Symfony ^6.1 +* Only support PHP ^8.1 +* Isolate model variables <> place variables <> transition variables on runtime +* Support transition expression, make it user friendly +* Add custom commands (upload, download, update/assert clipboard) + 2.0.0 ----- diff --git a/README.md b/README.md index 8096bd66..378eb17a 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ This Bundle is a core library for [Sicope Model][sicope-model], a Model-Based Te ## Requirements -* PHP 8.0 -* Symfony 5.3 +* PHP 8.1 +* Symfony 6.1 * See also the `require` section of [composer.json](composer.json) ## Installation @@ -66,7 +66,7 @@ This package is available under the [MIT license](LICENSE). [version-image]: https://img.shields.io/github/v/release/tienvx/mbt-bundle?include_prereleases [php-version-url]: https://packagist.org/packages/tienvx/mbt-bundle -[php-version-image]: http://img.shields.io/badge/php-8.0.0+-ff69b4.svg +[php-version-image]: http://img.shields.io/badge/php-8.1.0+-ff69b4.svg [contributors]: https://github.com/tienvx/mbt-bundle/graphs/contributors [pulls]: https://github.com/tienvx/mbt-bundle/pulls From 69ec5d96f23c175eecdaa46e021d995cbb12e32f Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 19 Jul 2022 21:47:22 +0700 Subject: [PATCH 75/85] Only push code coverage on push --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0097a5d5..b7360f68 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -44,4 +44,4 @@ jobs: run: | composer global require php-coveralls/php-coveralls php-coveralls --coverage_clover=clover.xml -v - if: matrix.php-versions == '8.1' + if: ${{ github.event_name == 'push' && matrix.php-versions == '8.1' }} From 73946f7fc00e77af9967e270dade7fcd367876fd Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 19 Jul 2022 21:51:55 +0700 Subject: [PATCH 76/85] Only upload code coverage if lowest dependency --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b7360f68..0236f913 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -44,4 +44,4 @@ jobs: run: | composer global require php-coveralls/php-coveralls php-coveralls --coverage_clover=clover.xml -v - if: ${{ github.event_name == 'push' && matrix.php-versions == '8.1' }} + if: ${{ github.event_name == 'push' && matrix.php-versions == '8.1' && matrix.dependency-versions == 'lowest' }} From 329a50b746c7e0d72ecfc008fc6c432d3da297f7 Mon Sep 17 00:00:00 2001 From: tienvx Date: Thu, 21 Jul 2022 08:47:22 +0700 Subject: [PATCH 77/85] Refactor commands --- composer.json | 2 +- config/services.php | 39 +- ...{CommandRunner.php => AbstractCommand.php} | 43 +- src/Command/Alert/AbstractAlertCommand.php | 23 + src/Command/Alert/AcceptAlertCommand.php | 25 + src/Command/Alert/AnswerPromptCommand.php | 37 + src/Command/Alert/DismissPromptCommand.php | 25 + src/Command/Assert/AbstractAssertCommand.php | 13 + src/Command/Assert/AssertAlertCommand.php | 39 ++ src/Command/Assert/AssertCheckedCommand.php | 28 + src/Command/Assert/AssertCommand.php | 44 ++ src/Command/Assert/AssertEditableCommand.php | 28 + .../Assert/AssertElementNotPresentCommand.php | 28 + .../Assert/AssertElementPresentCommand.php | 28 + .../Assert/AssertNotCheckedCommand.php | 28 + .../Assert/AssertNotEditableCommand.php | 28 + .../Assert/AssertNotSelectedLabelCommand.php | 35 + .../Assert/AssertNotSelectedValueCommand.php | 35 + src/Command/Assert/AssertNotTextCommand.php | 34 + .../Assert/AssertSelectedLabelCommand.php | 35 + .../Assert/AssertSelectedValueCommand.php | 35 + src/Command/Assert/AssertTextCommand.php | 34 + src/Command/Assert/AssertTitleCommand.php | 38 ++ src/Command/Assert/AssertValueCommand.php | 34 + src/Command/CommandInterface.php | 38 ++ src/Command/CommandManager.php | 102 +++ src/Command/CommandManagerInterface.php | 162 +++++ src/Command/CommandPreprocessor.php | 37 - src/Command/CommandPreprocessorInterface.php | 11 - src/Command/CommandRunnerInterface.php | 41 -- src/Command/CommandRunnerManager.php | 67 -- src/Command/CommandRunnerManagerInterface.php | 20 - src/Command/Custom/AbstractCustomCommand.php | 13 + .../Custom/AbstractHasHttpClientCommand.php | 33 + src/Command/Custom/AssertClipboardCommand.php | 42 ++ .../Custom/AssertFileDownloadedCommand.php | 42 ++ src/Command/Custom/UpdateClipboardCommand.php | 38 ++ src/Command/Custom/UploadCommand.php | 48 ++ .../Keyboard/AbstractKeyboardCommand.php | 18 + src/Command/Keyboard/SendKeysCommand.php | 33 + src/Command/Keyboard/TypeCommand.php | 34 + src/Command/Mouse/AbstractMouseCommand.php | 13 + .../Mouse/AbstractMousePointCommand.php | 25 + .../Mouse/AbstractMouseSelectionCommand.php | 11 + src/Command/Mouse/AddSelectionCommand.php | 37 + src/Command/Mouse/CheckCommand.php | 28 + src/Command/Mouse/ClickAtCommand.php | 30 + src/Command/Mouse/ClickCommand.php | 25 + src/Command/Mouse/DoubleClickAtCommand.php | 30 + src/Command/Mouse/DoubleClickCommand.php | 27 + .../Mouse/DragAndDropToObjectCommand.php | 43 ++ src/Command/Mouse/MouseDownAtCommand.php | 30 + src/Command/Mouse/MouseDownCommand.php | 27 + src/Command/Mouse/MouseMoveAtCommand.php | 30 + src/Command/Mouse/MouseOutCommand.php | 52 ++ src/Command/Mouse/MouseOverCommand.php | 27 + src/Command/Mouse/MouseUpAtCommand.php | 30 + src/Command/Mouse/MouseUpCommand.php | 27 + src/Command/Mouse/RemoveSelectionCommand.php | 37 + src/Command/Mouse/SelectCommand.php | 43 ++ src/Command/Mouse/UncheckCommand.php | 28 + src/Command/Runner/AlertCommandRunner.php | 68 -- src/Command/Runner/AssertionRunner.php | 227 ------- src/Command/Runner/CustomCommandRunner.php | 135 ---- src/Command/Runner/KeyboardCommandRunner.php | 61 -- src/Command/Runner/MouseCommandRunner.php | 228 ------- src/Command/Runner/ScriptCommandRunner.php | 65 -- src/Command/Runner/StoreCommandRunner.php | 138 ---- src/Command/Runner/WaitCommandRunner.php | 90 --- src/Command/Runner/WindowCommandRunner.php | 138 ---- src/Command/Script/AbstractScriptCommand.php | 18 + .../Script/ExecuteAsyncScriptCommand.php | 38 ++ src/Command/Script/ExecuteScriptCommand.php | 38 ++ src/Command/Script/RunScriptCommand.php | 30 + src/Command/Store/AbstractStoreCommand.php | 13 + src/Command/Store/StoreAttributeCommand.php | 56 ++ src/Command/Store/StoreCommand.php | 45 ++ .../Store/StoreElementCountCommand.php | 38 ++ src/Command/Store/StoreJsonCommand.php | 52 ++ src/Command/Store/StoreTextCommand.php | 38 ++ src/Command/Store/StoreTitleCommand.php | 35 + src/Command/Store/StoreValueCommand.php | 38 ++ .../Store/StoreWindowHandleCommand.php | 35 + src/Command/Wait/AbstractWaitCommand.php | 33 + .../Wait/WaitForElementEditableCommand.php | 21 + .../Wait/WaitForElementNotEditableCommand.php | 21 + .../Wait/WaitForElementNotPresentCommand.php | 21 + .../Wait/WaitForElementNotVisibleCommand.php | 18 + .../Wait/WaitForElementPresentCommand.php | 18 + .../Wait/WaitForElementVisibleCommand.php | 18 + src/Command/Window/AbstractWindowCommand.php | 18 + src/Command/Window/CloseCommand.php | 30 + src/Command/Window/OpenCommand.php | 40 ++ src/Command/Window/SelectFrameCommand.php | 53 ++ src/Command/Window/SelectWindowCommand.php | 40 ++ src/Command/Window/SetWindowSizeCommand.php | 43 ++ src/Service/Step/Runner/StepRunner.php | 16 +- src/TienvxMbtBundle.php | 5 +- src/Validator/ValidCommand.php | 3 +- src/Validator/ValidCommandValidator.php | 43 +- .../Command/Alert/AcceptAlertCommandTest.php | 55 ++ .../Command/Alert/AnswerPromptCommandTest.php | 56 ++ .../Alert/DismissPromptCommandTest.php | 55 ++ .../Command/Assert/AssertAlertCommandTest.php | 70 ++ .../Assert/AssertCheckedCommandTest.php | 77 +++ tests/Command/Assert/AssertCommandTest.php | 69 ++ .../Assert/AssertEditableCommandTest.php | 88 +++ .../AssertElementNotPresentCommandTest.php | 81 +++ .../AssertElementPresentCommandTest.php | 81 +++ .../Assert/AssertNotCheckedCommandTest.php | 77 +++ .../Assert/AssertNotEditableCommandTest.php | 88 +++ .../AssertNotSelectedLabelCommandTest.php | 83 +++ .../AssertNotSelectedValueCommandTest.php | 83 +++ .../Assert/AssertNotTextCommandTest.php | 80 +++ .../Assert/AssertSelectedLabelCommandTest.php | 83 +++ .../Assert/AssertSelectedValueCommandTest.php | 83 +++ .../Command/Assert/AssertTextCommandTest.php | 80 +++ .../Command/Assert/AssertTitleCommandTest.php | 64 ++ .../Command/Assert/AssertValueCommandTest.php | 81 +++ tests/Command/CommandManagerTest.php | 211 ++++++ tests/Command/CommandPreprocessorTest.php | 111 --- tests/Command/CommandRunnerManagerTest.php | 138 ---- tests/Command/CommandTestCase.php | 78 +++ .../Custom/AssertClipboardCommandTest.php | 98 +++ .../AssertFileDownloadedCommandTest.php | 95 +++ .../Custom/HasHttpClientCommandTestCase.php | 35 + .../Custom/UpdateClipboardCommandTest.php | 95 +++ tests/Command/Custom/UploadCommandTest.php | 73 ++ .../Command/Keyboard/SendKeysCommandTest.php | 59 ++ tests/Command/Keyboard/TypeCommandTest.php | 64 ++ .../Command/Mouse/AddSelectionCommandTest.php | 84 +++ tests/Command/Mouse/CheckCommandTest.php | 74 ++ tests/Command/Mouse/ClickAtCommandTest.php | 68 ++ tests/Command/Mouse/ClickCommandTest.php | 58 ++ .../Mouse/DoubleClickAtCommandTest.php | 68 ++ .../Command/Mouse/DoubleClickCommandTest.php | 62 ++ .../Mouse/DragAndDropToObjectCommandTest.php | 78 +++ .../Command/Mouse/MouseDownAtCommandTest.php | 70 ++ tests/Command/Mouse/MouseDownCommandTest.php | 64 ++ .../Command/Mouse/MouseMoveAtCommandTest.php | 69 ++ tests/Command/Mouse/MouseOutCommandTest.php | 118 ++++ tests/Command/Mouse/MouseOverCommandTest.php | 64 ++ tests/Command/Mouse/MouseUpAtCommandTest.php | 70 ++ tests/Command/Mouse/MouseUpCommandTest.php | 64 ++ .../Mouse/RemoveSelectionCommandTest.php | 84 +++ tests/Command/Mouse/SelectCommandTest.php | 65 ++ tests/Command/Mouse/UncheckCommandTest.php | 74 ++ .../Command/Runner/AlertCommandRunnerTest.php | 102 --- tests/Command/Runner/AssertionRunnerTest.php | 643 ------------------ .../Runner/CustomCommandRunnerTest.php | 243 ------- .../Runner/KeyboardCommandRunnerTest.php | 80 --- .../Command/Runner/MouseCommandRunnerTest.php | 616 ----------------- tests/Command/Runner/RunnerTestCase.php | 74 -- .../Runner/ScriptCommandRunnerTest.php | 88 --- .../Command/Runner/StoreCommandRunnerTest.php | 174 ----- .../Command/Runner/WaitCommandRunnerTest.php | 212 ------ .../Runner/WindowCommandRunnerTest.php | 166 ----- .../Script/ExecuteAsyncScriptCommandTest.php | 74 ++ .../Script/ExecuteScriptCommandTest.php | 69 ++ tests/Command/Script/RunScriptCommandTest.php | 53 ++ .../Store/StoreAttributeCommandTest.php | 72 ++ tests/Command/Store/StoreCommandTest.php | 49 ++ .../Store/StoreElementCountCommandTest.php | 58 ++ tests/Command/Store/StoreJsonCommandTest.php | 53 ++ tests/Command/Store/StoreTextCommandTest.php | 66 ++ tests/Command/Store/StoreTitleCommandTest.php | 50 ++ tests/Command/Store/StoreValueCommandTest.php | 63 ++ .../Store/StoreWindowHandleCommandTest.php | 50 ++ .../WaitForElementEditableCommandTest.php | 66 ++ .../WaitForElementNotEditableCommandTest.php | 66 ++ .../WaitForElementNotPresentCommandTest.php | 72 ++ .../WaitForElementNotVisibleCommandTest.php | 72 ++ .../Wait/WaitForElementPresentCommandTest.php | 61 ++ .../Wait/WaitForElementVisibleCommandTest.php | 66 ++ tests/Command/Wait/WaitTestCase.php | 34 + tests/Command/Window/CloseCommandTest.php | 49 ++ tests/Command/Window/OpenCommandTest.php | 50 ++ .../Command/Window/SelectFrameCommandTest.php | 95 +++ .../Window/SelectWindowCommandTest.php | 54 ++ .../Window/SetWindowSizeCommandTest.php | 65 ++ tests/Entity/Model/RevisionTest.php | 14 +- .../Model/Revision/CommandFactoryTest.php | 3 +- .../CustomConstraintValidatorFactory.php | 26 +- tests/Model/Model/Revision/CommandTest.php | 6 +- tests/Model/Model/Revision/PlaceTest.php | 7 +- tests/Model/Model/Revision/TransitionTest.php | 9 +- tests/Service/Step/Runner/StepRunnerTest.php | 66 +- tests/Validator/ValidCommandValidatorTest.php | 81 +-- translations/validators.en.php | 1 + 189 files changed, 7773 insertions(+), 4152 deletions(-) rename src/Command/{CommandRunner.php => AbstractCommand.php} (57%) create mode 100644 src/Command/Alert/AbstractAlertCommand.php create mode 100644 src/Command/Alert/AcceptAlertCommand.php create mode 100644 src/Command/Alert/AnswerPromptCommand.php create mode 100644 src/Command/Alert/DismissPromptCommand.php create mode 100644 src/Command/Assert/AbstractAssertCommand.php create mode 100644 src/Command/Assert/AssertAlertCommand.php create mode 100644 src/Command/Assert/AssertCheckedCommand.php create mode 100644 src/Command/Assert/AssertCommand.php create mode 100644 src/Command/Assert/AssertEditableCommand.php create mode 100644 src/Command/Assert/AssertElementNotPresentCommand.php create mode 100644 src/Command/Assert/AssertElementPresentCommand.php create mode 100644 src/Command/Assert/AssertNotCheckedCommand.php create mode 100644 src/Command/Assert/AssertNotEditableCommand.php create mode 100644 src/Command/Assert/AssertNotSelectedLabelCommand.php create mode 100644 src/Command/Assert/AssertNotSelectedValueCommand.php create mode 100644 src/Command/Assert/AssertNotTextCommand.php create mode 100644 src/Command/Assert/AssertSelectedLabelCommand.php create mode 100644 src/Command/Assert/AssertSelectedValueCommand.php create mode 100644 src/Command/Assert/AssertTextCommand.php create mode 100644 src/Command/Assert/AssertTitleCommand.php create mode 100644 src/Command/Assert/AssertValueCommand.php create mode 100644 src/Command/CommandInterface.php create mode 100644 src/Command/CommandManager.php create mode 100644 src/Command/CommandManagerInterface.php delete mode 100644 src/Command/CommandPreprocessor.php delete mode 100644 src/Command/CommandPreprocessorInterface.php delete mode 100644 src/Command/CommandRunnerInterface.php delete mode 100644 src/Command/CommandRunnerManager.php delete mode 100644 src/Command/CommandRunnerManagerInterface.php create mode 100644 src/Command/Custom/AbstractCustomCommand.php create mode 100644 src/Command/Custom/AbstractHasHttpClientCommand.php create mode 100644 src/Command/Custom/AssertClipboardCommand.php create mode 100644 src/Command/Custom/AssertFileDownloadedCommand.php create mode 100644 src/Command/Custom/UpdateClipboardCommand.php create mode 100644 src/Command/Custom/UploadCommand.php create mode 100644 src/Command/Keyboard/AbstractKeyboardCommand.php create mode 100644 src/Command/Keyboard/SendKeysCommand.php create mode 100644 src/Command/Keyboard/TypeCommand.php create mode 100644 src/Command/Mouse/AbstractMouseCommand.php create mode 100644 src/Command/Mouse/AbstractMousePointCommand.php create mode 100644 src/Command/Mouse/AbstractMouseSelectionCommand.php create mode 100644 src/Command/Mouse/AddSelectionCommand.php create mode 100644 src/Command/Mouse/CheckCommand.php create mode 100644 src/Command/Mouse/ClickAtCommand.php create mode 100644 src/Command/Mouse/ClickCommand.php create mode 100644 src/Command/Mouse/DoubleClickAtCommand.php create mode 100644 src/Command/Mouse/DoubleClickCommand.php create mode 100644 src/Command/Mouse/DragAndDropToObjectCommand.php create mode 100644 src/Command/Mouse/MouseDownAtCommand.php create mode 100644 src/Command/Mouse/MouseDownCommand.php create mode 100644 src/Command/Mouse/MouseMoveAtCommand.php create mode 100644 src/Command/Mouse/MouseOutCommand.php create mode 100644 src/Command/Mouse/MouseOverCommand.php create mode 100644 src/Command/Mouse/MouseUpAtCommand.php create mode 100644 src/Command/Mouse/MouseUpCommand.php create mode 100644 src/Command/Mouse/RemoveSelectionCommand.php create mode 100644 src/Command/Mouse/SelectCommand.php create mode 100644 src/Command/Mouse/UncheckCommand.php delete mode 100644 src/Command/Runner/AlertCommandRunner.php delete mode 100644 src/Command/Runner/AssertionRunner.php delete mode 100644 src/Command/Runner/CustomCommandRunner.php delete mode 100644 src/Command/Runner/KeyboardCommandRunner.php delete mode 100644 src/Command/Runner/MouseCommandRunner.php delete mode 100644 src/Command/Runner/ScriptCommandRunner.php delete mode 100644 src/Command/Runner/StoreCommandRunner.php delete mode 100644 src/Command/Runner/WaitCommandRunner.php delete mode 100644 src/Command/Runner/WindowCommandRunner.php create mode 100644 src/Command/Script/AbstractScriptCommand.php create mode 100644 src/Command/Script/ExecuteAsyncScriptCommand.php create mode 100644 src/Command/Script/ExecuteScriptCommand.php create mode 100644 src/Command/Script/RunScriptCommand.php create mode 100644 src/Command/Store/AbstractStoreCommand.php create mode 100644 src/Command/Store/StoreAttributeCommand.php create mode 100644 src/Command/Store/StoreCommand.php create mode 100644 src/Command/Store/StoreElementCountCommand.php create mode 100644 src/Command/Store/StoreJsonCommand.php create mode 100644 src/Command/Store/StoreTextCommand.php create mode 100644 src/Command/Store/StoreTitleCommand.php create mode 100644 src/Command/Store/StoreValueCommand.php create mode 100644 src/Command/Store/StoreWindowHandleCommand.php create mode 100644 src/Command/Wait/AbstractWaitCommand.php create mode 100644 src/Command/Wait/WaitForElementEditableCommand.php create mode 100644 src/Command/Wait/WaitForElementNotEditableCommand.php create mode 100644 src/Command/Wait/WaitForElementNotPresentCommand.php create mode 100644 src/Command/Wait/WaitForElementNotVisibleCommand.php create mode 100644 src/Command/Wait/WaitForElementPresentCommand.php create mode 100644 src/Command/Wait/WaitForElementVisibleCommand.php create mode 100644 src/Command/Window/AbstractWindowCommand.php create mode 100644 src/Command/Window/CloseCommand.php create mode 100644 src/Command/Window/OpenCommand.php create mode 100644 src/Command/Window/SelectFrameCommand.php create mode 100644 src/Command/Window/SelectWindowCommand.php create mode 100644 src/Command/Window/SetWindowSizeCommand.php create mode 100644 tests/Command/Alert/AcceptAlertCommandTest.php create mode 100644 tests/Command/Alert/AnswerPromptCommandTest.php create mode 100644 tests/Command/Alert/DismissPromptCommandTest.php create mode 100644 tests/Command/Assert/AssertAlertCommandTest.php create mode 100644 tests/Command/Assert/AssertCheckedCommandTest.php create mode 100644 tests/Command/Assert/AssertCommandTest.php create mode 100644 tests/Command/Assert/AssertEditableCommandTest.php create mode 100644 tests/Command/Assert/AssertElementNotPresentCommandTest.php create mode 100644 tests/Command/Assert/AssertElementPresentCommandTest.php create mode 100644 tests/Command/Assert/AssertNotCheckedCommandTest.php create mode 100644 tests/Command/Assert/AssertNotEditableCommandTest.php create mode 100644 tests/Command/Assert/AssertNotSelectedLabelCommandTest.php create mode 100644 tests/Command/Assert/AssertNotSelectedValueCommandTest.php create mode 100644 tests/Command/Assert/AssertNotTextCommandTest.php create mode 100644 tests/Command/Assert/AssertSelectedLabelCommandTest.php create mode 100644 tests/Command/Assert/AssertSelectedValueCommandTest.php create mode 100644 tests/Command/Assert/AssertTextCommandTest.php create mode 100644 tests/Command/Assert/AssertTitleCommandTest.php create mode 100644 tests/Command/Assert/AssertValueCommandTest.php create mode 100644 tests/Command/CommandManagerTest.php delete mode 100644 tests/Command/CommandPreprocessorTest.php delete mode 100644 tests/Command/CommandRunnerManagerTest.php create mode 100644 tests/Command/CommandTestCase.php create mode 100644 tests/Command/Custom/AssertClipboardCommandTest.php create mode 100644 tests/Command/Custom/AssertFileDownloadedCommandTest.php create mode 100644 tests/Command/Custom/HasHttpClientCommandTestCase.php create mode 100644 tests/Command/Custom/UpdateClipboardCommandTest.php create mode 100644 tests/Command/Custom/UploadCommandTest.php create mode 100644 tests/Command/Keyboard/SendKeysCommandTest.php create mode 100644 tests/Command/Keyboard/TypeCommandTest.php create mode 100644 tests/Command/Mouse/AddSelectionCommandTest.php create mode 100644 tests/Command/Mouse/CheckCommandTest.php create mode 100644 tests/Command/Mouse/ClickAtCommandTest.php create mode 100644 tests/Command/Mouse/ClickCommandTest.php create mode 100644 tests/Command/Mouse/DoubleClickAtCommandTest.php create mode 100644 tests/Command/Mouse/DoubleClickCommandTest.php create mode 100644 tests/Command/Mouse/DragAndDropToObjectCommandTest.php create mode 100644 tests/Command/Mouse/MouseDownAtCommandTest.php create mode 100644 tests/Command/Mouse/MouseDownCommandTest.php create mode 100644 tests/Command/Mouse/MouseMoveAtCommandTest.php create mode 100644 tests/Command/Mouse/MouseOutCommandTest.php create mode 100644 tests/Command/Mouse/MouseOverCommandTest.php create mode 100644 tests/Command/Mouse/MouseUpAtCommandTest.php create mode 100644 tests/Command/Mouse/MouseUpCommandTest.php create mode 100644 tests/Command/Mouse/RemoveSelectionCommandTest.php create mode 100644 tests/Command/Mouse/SelectCommandTest.php create mode 100644 tests/Command/Mouse/UncheckCommandTest.php delete mode 100644 tests/Command/Runner/AlertCommandRunnerTest.php delete mode 100644 tests/Command/Runner/AssertionRunnerTest.php delete mode 100644 tests/Command/Runner/CustomCommandRunnerTest.php delete mode 100644 tests/Command/Runner/KeyboardCommandRunnerTest.php delete mode 100644 tests/Command/Runner/MouseCommandRunnerTest.php delete mode 100644 tests/Command/Runner/RunnerTestCase.php delete mode 100644 tests/Command/Runner/ScriptCommandRunnerTest.php delete mode 100644 tests/Command/Runner/StoreCommandRunnerTest.php delete mode 100644 tests/Command/Runner/WaitCommandRunnerTest.php delete mode 100644 tests/Command/Runner/WindowCommandRunnerTest.php create mode 100644 tests/Command/Script/ExecuteAsyncScriptCommandTest.php create mode 100644 tests/Command/Script/ExecuteScriptCommandTest.php create mode 100644 tests/Command/Script/RunScriptCommandTest.php create mode 100644 tests/Command/Store/StoreAttributeCommandTest.php create mode 100644 tests/Command/Store/StoreCommandTest.php create mode 100644 tests/Command/Store/StoreElementCountCommandTest.php create mode 100644 tests/Command/Store/StoreJsonCommandTest.php create mode 100644 tests/Command/Store/StoreTextCommandTest.php create mode 100644 tests/Command/Store/StoreTitleCommandTest.php create mode 100644 tests/Command/Store/StoreValueCommandTest.php create mode 100644 tests/Command/Store/StoreWindowHandleCommandTest.php create mode 100644 tests/Command/Wait/WaitForElementEditableCommandTest.php create mode 100644 tests/Command/Wait/WaitForElementNotEditableCommandTest.php create mode 100644 tests/Command/Wait/WaitForElementNotPresentCommandTest.php create mode 100644 tests/Command/Wait/WaitForElementNotVisibleCommandTest.php create mode 100644 tests/Command/Wait/WaitForElementPresentCommandTest.php create mode 100644 tests/Command/Wait/WaitForElementVisibleCommandTest.php create mode 100644 tests/Command/Wait/WaitTestCase.php create mode 100644 tests/Command/Window/CloseCommandTest.php create mode 100644 tests/Command/Window/OpenCommandTest.php create mode 100644 tests/Command/Window/SelectFrameCommandTest.php create mode 100644 tests/Command/Window/SelectWindowCommandTest.php create mode 100644 tests/Command/Window/SetWindowSizeCommandTest.php diff --git a/composer.json b/composer.json index abc37f53..9d0fb9ee 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "symfony/config": "^6.1", "symfony/dependency-injection": "^6.1", "symfony/expression-language": "^6.1", - "symfony/http-client-contracts": "^3.1", + "symfony/http-client": "^6.1", "symfony/http-kernel": "^6.1", "symfony/messenger": "^6.1", "symfony/validator": "^6.1", diff --git a/config/services.php b/config/services.php index 0e57772a..0494fffb 100644 --- a/config/services.php +++ b/config/services.php @@ -13,11 +13,9 @@ use Tienvx\AssignmentsEvaluator\AssignmentsEvaluator; use Tienvx\Bundle\MbtBundle\Channel\ChannelManager; use Tienvx\Bundle\MbtBundle\Channel\ChannelManagerInterface; -use Tienvx\Bundle\MbtBundle\Command\CommandPreprocessor; -use Tienvx\Bundle\MbtBundle\Command\CommandPreprocessorInterface; use Tienvx\Bundle\MbtBundle\Command\CommandRunnerInterface; -use Tienvx\Bundle\MbtBundle\Command\CommandRunnerManager; -use Tienvx\Bundle\MbtBundle\Command\CommandRunnerManagerInterface; +use Tienvx\Bundle\MbtBundle\Command\CommandManager; +use Tienvx\Bundle\MbtBundle\Command\CommandManagerInterface; use Tienvx\Bundle\MbtBundle\Command\Runner\AlertCommandRunner; use Tienvx\Bundle\MbtBundle\Command\Runner\AssertionRunner; use Tienvx\Bundle\MbtBundle\Command\Runner\CustomCommandRunner; @@ -166,43 +164,18 @@ ->set(TagsValidator::class) ->set(ValidCommandValidator::class) ->args([ - service(CommandRunnerManagerInterface::class), + service(CommandManagerInterface::class), ]) ->tag('validator.constraint_validator', [ 'alias' => ValidCommandValidator::class, ]) // Commands - ->set(CommandPreprocessor::class) - ->alias(CommandPreprocessorInterface::class, CommandPreprocessor::class) - ->set(CommandRunnerManager::class) - ->args([ - tagged_iterator(CommandRunnerInterface::TAG), - service(CommandPreprocessorInterface::class), - ]) - ->alias(CommandRunnerManagerInterface::class, CommandRunnerManager::class) - - ->set(AlertCommandRunner::class) - ->autoconfigure(true) - ->set(AssertionRunner::class) - ->autoconfigure(true) - ->set(KeyboardCommandRunner::class) - ->autoconfigure(true) - ->set(MouseCommandRunner::class) - ->autoconfigure(true) - ->set(ScriptCommandRunner::class) - ->autoconfigure(true) - ->set(StoreCommandRunner::class) - ->autoconfigure(true) - ->set(WaitCommandRunner::class) - ->autoconfigure(true) - ->set(WindowCommandRunner::class) - ->autoconfigure(true) - ->set(CustomCommandRunner::class) - ->autoconfigure(true) + ->set(CommandManager::class) ->args([ service(HttpClientInterface::class), ]) + ->alias(CommandManagerInterface::class, CommandManager::class) // Repositories ->set(BugRepository::class) @@ -261,7 +234,7 @@ ->set(StepRunner::class) ->args([ - service(CommandRunnerManagerInterface::class), + service(CommandManagerInterface::class), ]) ->alias(StepRunnerInterface::class, StepRunner::class) diff --git a/src/Command/CommandRunner.php b/src/Command/AbstractCommand.php similarity index 57% rename from src/Command/CommandRunner.php rename to src/Command/AbstractCommand.php index 8ad4cb70..128b82a6 100644 --- a/src/Command/CommandRunner.php +++ b/src/Command/AbstractCommand.php @@ -2,28 +2,53 @@ namespace Tienvx\Bundle\MbtBundle\Command; -use Facebook\WebDriver\Exception\UnexpectedTagNameException; +use Exception; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverSelect; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; -use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; +use Tienvx\Bundle\MbtBundle\Model\ValuesInterface; -abstract class CommandRunner implements CommandRunnerInterface +abstract class AbstractCommand implements CommandInterface { - public function supports(CommandInterface $command): bool + public static function getValueHelper(): string { - return in_array($command->getCommand(), $this->getAllCommands()); + return ''; } - protected function isValidSelector(string $target): bool + public static function getTargetHelper(): string + { + return "Locator e.g. 'id=email' or 'css=.last-name'"; + } + + public static function validateTarget(?string $target): bool + { + return $target && static::isValidSelector($target); + } + + protected static function isValidSelector(string $target): bool { list($mechanism) = explode('=', $target, 2); return in_array($mechanism, static::MECHANISMS); } + public function validateValue(?string $value): bool + { + return !static::isValueRequired() || !is_null($value); + } + + public function run(?string $target, ?string $value, ValuesInterface $values, RemoteWebDriver $driver): void + { + if (static::isTargetRequired() && empty($target)) { + throw new UnexpectedValueException('Target is required'); + } + if (static::isValueRequired() && empty($value)) { + throw new UnexpectedValueException('Value is required'); + } + } + protected function getSelector(string $target): WebDriverBy { list($mechanism, $value) = explode('=', $target, 2); @@ -59,8 +84,10 @@ protected function getSelect(WebDriverElement $element): WebDriverSelect return new WebDriverSelect($element); } - public function validateTarget(CommandInterface $command): bool + protected function assert(bool $assertion, string $message): void { - return $command->getTarget() && $this->isValidSelector($command->getTarget()); + if (!$assertion) { + throw new Exception($message); + } } } diff --git a/src/Command/Alert/AbstractAlertCommand.php b/src/Command/Alert/AbstractAlertCommand.php new file mode 100644 index 00000000..99312aff --- /dev/null +++ b/src/Command/Alert/AbstractAlertCommand.php @@ -0,0 +1,23 @@ +switchTo()->alert()->accept(); + } +} diff --git a/src/Command/Alert/AnswerPromptCommand.php b/src/Command/Alert/AnswerPromptCommand.php new file mode 100644 index 00000000..b5880416 --- /dev/null +++ b/src/Command/Alert/AnswerPromptCommand.php @@ -0,0 +1,37 @@ +switchTo()->alert(); + $alert->sendKeys($target); + $alert->accept(); + } +} diff --git a/src/Command/Alert/DismissPromptCommand.php b/src/Command/Alert/DismissPromptCommand.php new file mode 100644 index 00000000..6c88c67e --- /dev/null +++ b/src/Command/Alert/DismissPromptCommand.php @@ -0,0 +1,25 @@ +switchTo()->alert()->dismiss(); + } +} diff --git a/src/Command/Assert/AbstractAssertCommand.php b/src/Command/Assert/AbstractAssertCommand.php new file mode 100644 index 00000000..ea3ee97f --- /dev/null +++ b/src/Command/Assert/AbstractAssertCommand.php @@ -0,0 +1,13 @@ +switchTo()->alert()->getText(); + $this->assert( + $alertText === $target, + sprintf('Actual alert text "%s" did not match "%s"', $alertText, $target) + ); + } +} diff --git a/src/Command/Assert/AssertCheckedCommand.php b/src/Command/Assert/AssertCheckedCommand.php new file mode 100644 index 00000000..5ce9bd88 --- /dev/null +++ b/src/Command/Assert/AssertCheckedCommand.php @@ -0,0 +1,28 @@ +assert( + $driver->findElement($this->getSelector($target))->isSelected(), + sprintf('Element "%s" is not checked, expected to be checked', $target) + ); + } +} diff --git a/src/Command/Assert/AssertCommand.php b/src/Command/Assert/AssertCommand.php new file mode 100644 index 00000000..f229187b --- /dev/null +++ b/src/Command/Assert/AssertCommand.php @@ -0,0 +1,44 @@ +getValue($target); + $this->assert( + $actual === $value, + sprintf('Actual value "%s" did not match "%s"', $actual, $value) + ); + } +} diff --git a/src/Command/Assert/AssertEditableCommand.php b/src/Command/Assert/AssertEditableCommand.php new file mode 100644 index 00000000..e6b20c81 --- /dev/null +++ b/src/Command/Assert/AssertEditableCommand.php @@ -0,0 +1,28 @@ +assert( + $this->isElementEditable($driver, $driver->findElement($this->getSelector($target))), + sprintf('Element "%s" is not editable', $target) + ); + } +} diff --git a/src/Command/Assert/AssertElementNotPresentCommand.php b/src/Command/Assert/AssertElementNotPresentCommand.php new file mode 100644 index 00000000..9150a468 --- /dev/null +++ b/src/Command/Assert/AssertElementNotPresentCommand.php @@ -0,0 +1,28 @@ +assert( + 0 === count($driver->findElements($this->getSelector($target))), + sprintf('Unexpected element "%s" was found in page', $target) + ); + } +} diff --git a/src/Command/Assert/AssertElementPresentCommand.php b/src/Command/Assert/AssertElementPresentCommand.php new file mode 100644 index 00000000..96a791a6 --- /dev/null +++ b/src/Command/Assert/AssertElementPresentCommand.php @@ -0,0 +1,28 @@ +assert( + count($driver->findElements($this->getSelector($target))) > 0, + sprintf('Expected element "%s" was not found in page', $target) + ); + } +} diff --git a/src/Command/Assert/AssertNotCheckedCommand.php b/src/Command/Assert/AssertNotCheckedCommand.php new file mode 100644 index 00000000..4a5e66a6 --- /dev/null +++ b/src/Command/Assert/AssertNotCheckedCommand.php @@ -0,0 +1,28 @@ +assert( + !$driver->findElement($this->getSelector($target))->isSelected(), + sprintf('Element "%s" is checked, expected to be unchecked', $target) + ); + } +} diff --git a/src/Command/Assert/AssertNotEditableCommand.php b/src/Command/Assert/AssertNotEditableCommand.php new file mode 100644 index 00000000..48c9b745 --- /dev/null +++ b/src/Command/Assert/AssertNotEditableCommand.php @@ -0,0 +1,28 @@ +assert( + !$this->isElementEditable($driver, $driver->findElement($this->getSelector($target))), + sprintf('Element "%s" is editable', $target) + ); + } +} diff --git a/src/Command/Assert/AssertNotSelectedLabelCommand.php b/src/Command/Assert/AssertNotSelectedLabelCommand.php new file mode 100644 index 00000000..8b4b6ce6 --- /dev/null +++ b/src/Command/Assert/AssertNotSelectedLabelCommand.php @@ -0,0 +1,35 @@ +getSelect($driver->findElement($this->getSelector($target))); + $elementLabel = $select->getFirstSelectedOption()->getText(); + $this->assert( + $elementLabel !== $value, + sprintf('Actual label "%s" did match "%s"', $elementLabel, $value) + ); + } +} diff --git a/src/Command/Assert/AssertNotSelectedValueCommand.php b/src/Command/Assert/AssertNotSelectedValueCommand.php new file mode 100644 index 00000000..edd54a8c --- /dev/null +++ b/src/Command/Assert/AssertNotSelectedValueCommand.php @@ -0,0 +1,35 @@ +getSelect($driver->findElement($this->getSelector($target))); + $elementValue = $select->getFirstSelectedOption()->getAttribute('value'); + $this->assert( + $elementValue !== $value, + sprintf('Actual value "%s" did match "%s"', $elementValue, $value) + ); + } +} diff --git a/src/Command/Assert/AssertNotTextCommand.php b/src/Command/Assert/AssertNotTextCommand.php new file mode 100644 index 00000000..0c723316 --- /dev/null +++ b/src/Command/Assert/AssertNotTextCommand.php @@ -0,0 +1,34 @@ +findElement($this->getSelector($target))->getText(); + $this->assert( + $elementText !== $value, + sprintf('Actual text "%s" did match "%s"', $elementText, $value) + ); + } +} diff --git a/src/Command/Assert/AssertSelectedLabelCommand.php b/src/Command/Assert/AssertSelectedLabelCommand.php new file mode 100644 index 00000000..820368af --- /dev/null +++ b/src/Command/Assert/AssertSelectedLabelCommand.php @@ -0,0 +1,35 @@ +getSelect($driver->findElement($this->getSelector($target))); + $elementLabel = $select->getFirstSelectedOption()->getText(); + $this->assert( + $elementLabel === $value, + sprintf('Actual label "%s" did not match "%s"', $elementLabel, $value) + ); + } +} diff --git a/src/Command/Assert/AssertSelectedValueCommand.php b/src/Command/Assert/AssertSelectedValueCommand.php new file mode 100644 index 00000000..94f64cb7 --- /dev/null +++ b/src/Command/Assert/AssertSelectedValueCommand.php @@ -0,0 +1,35 @@ +getSelect($driver->findElement($this->getSelector($target))); + $elementValue = $select->getFirstSelectedOption()->getAttribute('value'); + $this->assert( + $elementValue === $value, + sprintf('Actual value "%s" did not match "%s"', $elementValue, $value) + ); + } +} diff --git a/src/Command/Assert/AssertTextCommand.php b/src/Command/Assert/AssertTextCommand.php new file mode 100644 index 00000000..a2a600c2 --- /dev/null +++ b/src/Command/Assert/AssertTextCommand.php @@ -0,0 +1,34 @@ +findElement($this->getSelector($target))->getText(); + $this->assert( + $elementText === $value, + sprintf('Actual text "%s" did not match "%s"', $elementText, $value) + ); + } +} diff --git a/src/Command/Assert/AssertTitleCommand.php b/src/Command/Assert/AssertTitleCommand.php new file mode 100644 index 00000000..324f1712 --- /dev/null +++ b/src/Command/Assert/AssertTitleCommand.php @@ -0,0 +1,38 @@ +assert( + $driver->getTitle() === $target, + sprintf('Actual title "%s" did not match "%s"', $driver->getTitle(), $target) + ); + } +} diff --git a/src/Command/Assert/AssertValueCommand.php b/src/Command/Assert/AssertValueCommand.php new file mode 100644 index 00000000..1077d1f2 --- /dev/null +++ b/src/Command/Assert/AssertValueCommand.php @@ -0,0 +1,34 @@ +findElement($this->getSelector($target))->getAttribute('value'); + $this->assert( + $elementValue === $value, + sprintf('Actual value "%s" did not match "%s"', $elementValue, $value) + ); + } +} diff --git a/src/Command/CommandInterface.php b/src/Command/CommandInterface.php new file mode 100644 index 00000000..32203505 --- /dev/null +++ b/src/Command/CommandInterface.php @@ -0,0 +1,38 @@ +uploadDir = $uploadDir; + } + + public function hasCommand(string $command): bool + { + return isset(static::COMMANDS[$command]); + } + + public function isTargetMissing(string $command, ?string $target): bool + { + return $this->hasCommand($command) && + call_user_func([static::COMMANDS[$command], 'isTargetRequired']) && + empty($target); + } + + public function isTargetNotValid(string $command, ?string $target): bool + { + return $this->hasCommand($command) && + !call_user_func([static::COMMANDS[$command], 'validateTarget'], $target); + } + + public function isValueMissing(string $command, ?string $value): bool + { + return $this->hasCommand($command) && + call_user_func([static::COMMANDS[$command], 'isValueRequired']) && + empty($value); + } + + public function isValueNotValid(string $command, ?string $value): bool + { + return $this->hasCommand($command) && + !call_user_func([$this->createCommand($command), 'validateValue'], $value); + } + + public function run( + string $command, + ?string $target, + ?string $value, + ValuesInterface $values, + RemoteWebDriver $driver + ): void { + $this->createCommand($command)->run( + $this->process($target, $values), + $this->process($value, $values), + $values, + $driver + ); + } + + protected function createCommand(string $command): CommandInterface + { + switch ($command) { + case 'upload': + return new UploadCommand($this->uploadDir); + case 'assertFileDownloaded': + return new AssertFileDownloadedCommand($this->httpClient); + case 'assertClipboard': + return new AssertClipboardCommand($this->httpClient); + case 'updateClipboard': + return new UpdateClipboardCommand($this->httpClient); + default: + $className = static::COMMANDS[$command] ?? null; + if ($className) { + return new $className(); + } + + throw new OutOfRangeException(sprintf('Command %s not found', $command)); + } + } + + protected function process(?string $text, ValuesInterface $values): ?string + { + return is_string($text) ? preg_replace_callback( + '/\$\{(.*?)\}/', + fn (array $matches): string => $values->getValues()[$matches[1]] ?? $matches[1], + $text + ) : null; + } +} diff --git a/src/Command/CommandManagerInterface.php b/src/Command/CommandManagerInterface.php new file mode 100644 index 00000000..59f9875c --- /dev/null +++ b/src/Command/CommandManagerInterface.php @@ -0,0 +1,162 @@ + AcceptAlertCommand::class, + 'acceptConfirmation' => AcceptAlertCommand::class, + 'answerPrompt' => AnswerPromptCommand::class, + 'dismissConfirmation' => DismissPromptCommand::class, + 'dismissPrompt' => DismissPromptCommand::class, + 'assert' => AssertCommand::class, + 'assertAlert' => AssertAlertCommand::class, + 'assertConfirmation' => AssertAlertCommand::class, + 'assertPrompt' => AssertAlertCommand::class, + 'assertTitle' => AssertTitleCommand::class, + 'assertText' => AssertTextCommand::class, + 'assertNotText' => AssertNotTextCommand::class, + 'assertValue' => AssertValueCommand::class, + 'assertEditable' => AssertEditableCommand::class, + 'assertNotEditable' => AssertNotEditableCommand::class, + 'assertElementPresent' => AssertElementPresentCommand::class, + 'assertElementNotPresent' => AssertElementNotPresentCommand::class, + 'assertChecked' => AssertCheckedCommand::class, + 'assertNotChecked' => AssertNotCheckedCommand::class, + 'assertSelectedValue' => AssertSelectedValueCommand::class, + 'assertNotSelectedValue' => AssertNotSelectedValueCommand::class, + 'assertSelectedLabel' => AssertSelectedLabelCommand::class, + 'assertNotSelectedLabel' => AssertNotSelectedLabelCommand::class, + 'upload' => UploadCommand::class, + 'assertFileDownloaded' => AssertFileDownloadedCommand::class, + 'assertClipboard' => AssertClipboardCommand::class, + 'updateClipboard' => UpdateClipboardCommand::class, + 'type' => TypeCommand::class, + 'sendKeys' => SendKeysCommand::class, + 'addSelection' => AddSelectionCommand::class, + 'removeSelection' => RemoveSelectionCommand::class, + 'check' => CheckCommand::class, + 'uncheck' => UncheckCommand::class, + 'click' => ClickCommand::class, + 'clickAt' => ClickAtCommand::class, + 'doubleClick' => DoubleClickCommand::class, + 'doubleClickAt' => DoubleClickAtCommand::class, + 'dragAndDropToObject' => DragAndDropToObjectCommand::class, + 'mouseDown' => MouseDownCommand::class, + 'mouseDownAt' => MouseDownAtCommand::class, + 'mouseMoveAt' => MouseMoveAtCommand::class, + 'mouseOut' => MouseOutCommand::class, + 'mouseOver' => MouseOverCommand::class, + 'mouseUp' => MouseUpCommand::class, + 'mouseUpAt' => MouseUpAtCommand::class, + 'select' => SelectCommand::class, + 'runScript' => RunScriptCommand::class, + 'executeScript' => ExecuteScriptCommand::class, + 'executeAsyncScript' => ExecuteAsyncScriptCommand::class, + 'store' => StoreCommand::class, + 'storeAttribute' => StoreAttributeCommand::class, + 'storeElementCount' => StoreElementCountCommand::class, + 'storeJson' => StoreJsonCommand::class, + 'storeText' => StoreTextCommand::class, + 'storeTitle' => StoreTitleCommand::class, + 'storeValue' => StoreValueCommand::class, + 'storeWindowHandle' => StoreWindowHandleCommand::class, + 'waitForElementEditable' => WaitForElementEditableCommand::class, + 'waitForElementNotEditable' => WaitForElementNotEditableCommand::class, + 'waitForElementPresent' => WaitForElementPresentCommand::class, + 'waitForElementNotPresent' => WaitForElementNotPresentCommand::class, + 'waitForElementVisible' => WaitForElementVisibleCommand::class, + 'waitForElementNotVisible' => WaitForElementNotVisibleCommand::class, + 'open' => OpenCommand::class, + 'setWindowSize' => SetWindowSizeCommand::class, + 'selectWindow' => SelectWindowCommand::class, + 'close' => CloseCommand::class, + 'selectFrame' => SelectFrameCommand::class, + ]; + + public function hasCommand(string $command): bool; + + public function isTargetMissing(string $command, ?string $target): bool; + + public function isTargetNotValid(string $command, ?string $target): bool; + + public function isValueMissing(string $command, ?string $value): bool; + + public function isValueNotValid(string $command, ?string $value): bool; + + public function run( + string $command, + ?string $target, + ?string $value, + ValuesInterface $values, + RemoteWebDriver $driver + ): void; +} diff --git a/src/Command/CommandPreprocessor.php b/src/Command/CommandPreprocessor.php deleted file mode 100644 index 5ad0c5e9..00000000 --- a/src/Command/CommandPreprocessor.php +++ /dev/null @@ -1,37 +0,0 @@ -setCommand($command->getCommand()); - $processed->setTarget( - $command->getTarget() - ? $this->replaceVariables($command->getTarget(), $values->getValues()) - : $command->getTarget() - ); - $processed->setValue( - $command->getValue() - ? $this->replaceVariables($command->getValue(), $values->getValues()) - : $command->getValue() - ); - - return $processed; - } - - protected function replaceVariables(string $text, array $values): string - { - return preg_replace_callback( - '/\$\{(.*?)\}/', - fn (array $matches): string => $values[$matches[1]] ?? $matches[1], - $text - ); - } -} diff --git a/src/Command/CommandPreprocessorInterface.php b/src/Command/CommandPreprocessorInterface.php deleted file mode 100644 index 8e72880e..00000000 --- a/src/Command/CommandPreprocessorInterface.php +++ /dev/null @@ -1,11 +0,0 @@ -runners = $runners; - $this->commandPreprocessor = $commandPreprocessor; - } - - public function getAllCommands(): array - { - return $this->getCommands(); - } - - public function getCommandsRequireTarget(): array - { - return $this->getCommands('getCommandsRequireTarget'); - } - - public function getCommandsRequireValue(): array - { - return $this->getCommands('getCommandsRequireValue'); - } - - public function validateTarget(CommandInterface $command): bool - { - foreach ($this->runners as $runner) { - if ($runner instanceof CommandRunnerInterface && $runner->supports($command)) { - return $runner->validateTarget($command); - } - } - - return false; - } - - public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void - { - foreach ($this->runners as $runner) { - if ($runner instanceof CommandRunnerInterface && $runner->supports($command)) { - $runner->run($this->commandPreprocessor->process($command, $values), $values, $driver); - break; - } - } - } - - protected function getCommands(string $method = 'getAllCommands'): array - { - $commands = []; - foreach ($this->runners as $runner) { - if ($runner instanceof CommandRunnerInterface) { - $commands = array_merge($commands, call_user_func([$runner, $method])); - } - } - - return $commands; - } -} diff --git a/src/Command/CommandRunnerManagerInterface.php b/src/Command/CommandRunnerManagerInterface.php deleted file mode 100644 index d68e3d88..00000000 --- a/src/Command/CommandRunnerManagerInterface.php +++ /dev/null @@ -1,20 +0,0 @@ -getCommandExecutor()->getAddressOfRemoteServer(), '/wd/hub'), + $type, + $driver->getSessionID() + ); + } +} diff --git a/src/Command/Custom/AssertClipboardCommand.php b/src/Command/Custom/AssertClipboardCommand.php new file mode 100644 index 00000000..bab366ea --- /dev/null +++ b/src/Command/Custom/AssertClipboardCommand.php @@ -0,0 +1,42 @@ +httpClient->request( + 'GET', + $this->getUrl('clipboard', $driver) + )->getContent(); + $this->assert($clipboard === $target, sprintf( + "Failed expecting that clipboard's content equals '%s', actual value '%s'", + $target, + $clipboard + )); + } catch (ExceptionInterface $e) { + throw new RuntimeException(sprintf( + 'Can not get clipboard: %s', + $e->getMessage() + )); + } + } +} diff --git a/src/Command/Custom/AssertFileDownloadedCommand.php b/src/Command/Custom/AssertFileDownloadedCommand.php new file mode 100644 index 00000000..6b9891d7 --- /dev/null +++ b/src/Command/Custom/AssertFileDownloadedCommand.php @@ -0,0 +1,42 @@ +httpClient->request( + 'GET', + sprintf('%s/%s', $this->getUrl('download', $driver), $target) + )->getStatusCode(); + $this->assert(200 === $code, sprintf( + 'Failed expecting that file %s is downloaded', + $target + )); + } catch (ExceptionInterface $e) { + throw new RuntimeException(sprintf( + 'Can not get downloaded file %s: %s', + $target, + $e->getMessage() + )); + } + } +} diff --git a/src/Command/Custom/UpdateClipboardCommand.php b/src/Command/Custom/UpdateClipboardCommand.php new file mode 100644 index 00000000..200f5fd3 --- /dev/null +++ b/src/Command/Custom/UpdateClipboardCommand.php @@ -0,0 +1,38 @@ +httpClient->request( + 'POST', + $this->getUrl('clipboard', $driver), + ['body' => $target] + )->getStatusCode(); + } catch (ExceptionInterface $e) { + throw new RuntimeException(sprintf( + 'Can not update clipboard: %s', + $e->getMessage() + )); + } + } +} diff --git a/src/Command/Custom/UploadCommand.php b/src/Command/Custom/UploadCommand.php new file mode 100644 index 00000000..72da07d1 --- /dev/null +++ b/src/Command/Custom/UploadCommand.php @@ -0,0 +1,48 @@ +getFilePath($value)); + } + + public function run(?string $target, ?string $value, ValuesInterface $values, RemoteWebDriver $driver): void + { + parent::run($target, $value, $values, $driver); + $driver + ->findElement($this->getSelector($target)) + ->setFileDetector(new LocalFileDetector()) + ->sendKeys($this->getFilePath($value)); + } + + protected function getFilePath(string $value): string + { + return $this->uploadDir . DIRECTORY_SEPARATOR . $value; + } +} diff --git a/src/Command/Keyboard/AbstractKeyboardCommand.php b/src/Command/Keyboard/AbstractKeyboardCommand.php new file mode 100644 index 00000000..b96ee109 --- /dev/null +++ b/src/Command/Keyboard/AbstractKeyboardCommand.php @@ -0,0 +1,18 @@ +findElement($this->getSelector($target)) + ->click() + ->sendKeys($value); + } +} diff --git a/src/Command/Keyboard/TypeCommand.php b/src/Command/Keyboard/TypeCommand.php new file mode 100644 index 00000000..2e653e5e --- /dev/null +++ b/src/Command/Keyboard/TypeCommand.php @@ -0,0 +1,34 @@ +findElement($this->getSelector($target)) + ->click() + ->clear() + ->sendKeys($value); + } +} diff --git a/src/Command/Mouse/AbstractMouseCommand.php b/src/Command/Mouse/AbstractMouseCommand.php new file mode 100644 index 00000000..9e2dd6e7 --- /dev/null +++ b/src/Command/Mouse/AbstractMouseCommand.php @@ -0,0 +1,13 @@ +getSelect($driver->findElement($this->getSelector($target))); + if (str_starts_with($value, 'index=')) { + $select->selectByIndex((int) substr($value, 6)); + } elseif (str_starts_with($value, 'value=')) { + $select->selectByValue(substr($value, 6)); + } elseif (str_starts_with($value, 'label=')) { + $select->selectByVisibleText(substr($value, 6)); + } + } +} diff --git a/src/Command/Mouse/CheckCommand.php b/src/Command/Mouse/CheckCommand.php new file mode 100644 index 00000000..f55f482d --- /dev/null +++ b/src/Command/Mouse/CheckCommand.php @@ -0,0 +1,28 @@ +findElement($this->getSelector($target)); + if (!$element->isSelected()) { + $element->click(); + } + } +} diff --git a/src/Command/Mouse/ClickAtCommand.php b/src/Command/Mouse/ClickAtCommand.php new file mode 100644 index 00000000..e9085101 --- /dev/null +++ b/src/Command/Mouse/ClickAtCommand.php @@ -0,0 +1,30 @@ +getPoint($value); + $driver->action()->moveToElement( + $driver->findElement($this->getSelector($target)), + $point->getX(), + $point->getY() + )->click()->perform(); + } +} diff --git a/src/Command/Mouse/ClickCommand.php b/src/Command/Mouse/ClickCommand.php new file mode 100644 index 00000000..61d6ec82 --- /dev/null +++ b/src/Command/Mouse/ClickCommand.php @@ -0,0 +1,25 @@ +findElement($this->getSelector($target))->click(); + } +} diff --git a/src/Command/Mouse/DoubleClickAtCommand.php b/src/Command/Mouse/DoubleClickAtCommand.php new file mode 100644 index 00000000..e85d07ce --- /dev/null +++ b/src/Command/Mouse/DoubleClickAtCommand.php @@ -0,0 +1,30 @@ +getPoint($value); + $driver->action()->moveToElement( + $driver->findElement($this->getSelector($target)), + $point->getX(), + $point->getY() + )->doubleClick()->perform(); + } +} diff --git a/src/Command/Mouse/DoubleClickCommand.php b/src/Command/Mouse/DoubleClickCommand.php new file mode 100644 index 00000000..51821d3f --- /dev/null +++ b/src/Command/Mouse/DoubleClickCommand.php @@ -0,0 +1,27 @@ +action()->doubleClick( + $driver->findElement($this->getSelector($target)) + )->perform(); + } +} diff --git a/src/Command/Mouse/DragAndDropToObjectCommand.php b/src/Command/Mouse/DragAndDropToObjectCommand.php new file mode 100644 index 00000000..97b0dfd6 --- /dev/null +++ b/src/Command/Mouse/DragAndDropToObjectCommand.php @@ -0,0 +1,43 @@ +action()->dragAndDrop( + $driver->findElement($this->getSelector($target)), + $driver->findElement($this->getSelector($value)) + )->perform(); + } +} diff --git a/src/Command/Mouse/MouseDownAtCommand.php b/src/Command/Mouse/MouseDownAtCommand.php new file mode 100644 index 00000000..25e9d9b8 --- /dev/null +++ b/src/Command/Mouse/MouseDownAtCommand.php @@ -0,0 +1,30 @@ +getPoint($value); + $driver->getMouse()->mouseMove( + $driver->findElement($this->getSelector($target))->getCoordinates(), + $point->getX(), + $point->getY() + )->mouseDown(); + } +} diff --git a/src/Command/Mouse/MouseDownCommand.php b/src/Command/Mouse/MouseDownCommand.php new file mode 100644 index 00000000..ed1b61f6 --- /dev/null +++ b/src/Command/Mouse/MouseDownCommand.php @@ -0,0 +1,27 @@ +getMouse()->mouseDown( + $driver->findElement($this->getSelector($target))->getCoordinates() + ); + } +} diff --git a/src/Command/Mouse/MouseMoveAtCommand.php b/src/Command/Mouse/MouseMoveAtCommand.php new file mode 100644 index 00000000..0367b309 --- /dev/null +++ b/src/Command/Mouse/MouseMoveAtCommand.php @@ -0,0 +1,30 @@ +getPoint($value); + $driver->getMouse()->mouseMove( + $driver->findElement($this->getSelector($target))->getCoordinates(), + $point->getX(), + $point->getY() + ); + } +} diff --git a/src/Command/Mouse/MouseOutCommand.php b/src/Command/Mouse/MouseOutCommand.php new file mode 100644 index 00000000..cb78ced2 --- /dev/null +++ b/src/Command/Mouse/MouseOutCommand.php @@ -0,0 +1,52 @@ +findElement($this->getSelector($target)); + [$rect, $vp] = $driver->executeScript( + // phpcs:ignore Generic.Files.LineLength + 'return [arguments[0].getBoundingClientRect(), {height: window.innerHeight, width: window.innerWidth}];', + [$element] + ); + if ($rect->top > 0) { + // try top + $y = -($rect->height / 2 + 1); + $driver->getMouse()->mouseMove($element->getCoordinates(), null, $y); + } elseif ($vp->width > $rect->right) { + // try right + $x = $rect->right / 2 + 1; + $driver->getMouse()->mouseMove($element->getCoordinates(), $x); + } elseif ($vp->height > $rect->bottom) { + // try bottom + $y = $rect->height / 2 + 1; + $driver->getMouse()->mouseMove($element->getCoordinates(), null, $y); + } elseif ($rect->left > 0) { + // try left + $x = (int) (-($rect->right / 2)); + $driver->getMouse()->mouseMove($element->getCoordinates(), $x); + } else { + throw new Exception( + 'Unable to perform mouse out as the element takes up the entire viewport' + ); + } + } +} diff --git a/src/Command/Mouse/MouseOverCommand.php b/src/Command/Mouse/MouseOverCommand.php new file mode 100644 index 00000000..a6368411 --- /dev/null +++ b/src/Command/Mouse/MouseOverCommand.php @@ -0,0 +1,27 @@ +getMouse()->mouseMove( + $driver->findElement($this->getSelector($target))->getCoordinates() + ); + } +} diff --git a/src/Command/Mouse/MouseUpAtCommand.php b/src/Command/Mouse/MouseUpAtCommand.php new file mode 100644 index 00000000..2c12a29b --- /dev/null +++ b/src/Command/Mouse/MouseUpAtCommand.php @@ -0,0 +1,30 @@ +getPoint($value); + $driver->getMouse()->mouseMove( + $driver->findElement($this->getSelector($target))->getCoordinates(), + $point->getX(), + $point->getY() + )->mouseUp(); + } +} diff --git a/src/Command/Mouse/MouseUpCommand.php b/src/Command/Mouse/MouseUpCommand.php new file mode 100644 index 00000000..fc57dee5 --- /dev/null +++ b/src/Command/Mouse/MouseUpCommand.php @@ -0,0 +1,27 @@ +getMouse()->mouseUp( + $driver->findElement($this->getSelector($target))->getCoordinates() + ); + } +} diff --git a/src/Command/Mouse/RemoveSelectionCommand.php b/src/Command/Mouse/RemoveSelectionCommand.php new file mode 100644 index 00000000..28acda3d --- /dev/null +++ b/src/Command/Mouse/RemoveSelectionCommand.php @@ -0,0 +1,37 @@ +getSelect($driver->findElement($this->getSelector($target))); + if (str_starts_with($value, 'index=')) { + $select->deselectByIndex((int) substr($value, 6)); + } elseif (str_starts_with($value, 'value=')) { + $select->deselectByValue(substr($value, 6)); + } elseif (str_starts_with($value, 'label=')) { + $select->deselectByVisibleText(substr($value, 6)); + } + } +} diff --git a/src/Command/Mouse/SelectCommand.php b/src/Command/Mouse/SelectCommand.php new file mode 100644 index 00000000..7c46d599 --- /dev/null +++ b/src/Command/Mouse/SelectCommand.php @@ -0,0 +1,43 @@ +findElement($this->getSelector($target)) + ->findElement($this->getSelector($value)) + ->click(); + } +} diff --git a/src/Command/Mouse/UncheckCommand.php b/src/Command/Mouse/UncheckCommand.php new file mode 100644 index 00000000..9e18ab8d --- /dev/null +++ b/src/Command/Mouse/UncheckCommand.php @@ -0,0 +1,28 @@ +findElement($this->getSelector($target)); + if ($element->isSelected()) { + $element->click(); + } + } +} diff --git a/src/Command/Runner/AlertCommandRunner.php b/src/Command/Runner/AlertCommandRunner.php deleted file mode 100644 index a09787e6..00000000 --- a/src/Command/Runner/AlertCommandRunner.php +++ /dev/null @@ -1,68 +0,0 @@ -getCommand()) { - case self::ACCEPT_ALERT: - case self::ACCEPT_CONFIRMATION: - $driver->switchTo()->alert()->accept(); - break; - case self::ANSWER_PROMPT: - $alert = $driver->switchTo()->alert(); - if ($command->getTarget()) { - $alert->sendKeys($command->getTarget()); - } - $alert->accept(); - break; - case self::DISMISS_CONFIRMATION: - case self::DISMISS_PROMPT: - $driver->switchTo()->alert()->dismiss(); - break; - default: - break; - } - } - - public function validateTarget(CommandInterface $command): bool - { - return true; - } -} diff --git a/src/Command/Runner/AssertionRunner.php b/src/Command/Runner/AssertionRunner.php deleted file mode 100644 index 21466672..00000000 --- a/src/Command/Runner/AssertionRunner.php +++ /dev/null @@ -1,227 +0,0 @@ -getAllCommands(); - } - - public function getCommandsRequireValue(): array - { - return [ - self::ASSERT, - self::ASSERT_TEXT, - self::ASSERT_NOT_TEXT, - self::ASSERT_VALUE, - self::ASSERT_SELECTED_VALUE, - self::ASSERT_NOT_SELECTED_VALUE, - self::ASSERT_SELECTED_LABEL, - self::ASSERT_NOT_SELECTED_LABEL, - ]; - } - - public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void - { - switch ($command->getCommand()) { - case self::ASSERT: - $actual = $values->getValue($command->getTarget()); - $this->assert( - $actual === $command->getValue(), - sprintf('Actual value "%s" did not match "%s"', $actual, $command->getValue()) - ); - break; - case self::ASSERT_ALERT: - case self::ASSERT_CONFIRMATION: - case self::ASSERT_PROMPT: - $alertText = $driver->switchTo()->alert()->getText(); - $type = [ - self::ASSERT_ALERT => 'alert', - self::ASSERT_CONFIRMATION => 'confirm', - self::ASSERT_PROMPT => 'prompt', - ][$command->getCommand()]; - $this->assert( - $alertText === $command->getTarget(), - sprintf('Actual %s text "%s" did not match "%s"', $type, $alertText, $command->getTarget()) - ); - break; - case self::ASSERT_TITLE: - $this->assert( - $driver->getTitle() === $command->getTarget(), - sprintf('Actual title "%s" did not match "%s"', $driver->getTitle(), $command->getTarget()) - ); - break; - case self::ASSERT_TEXT: - $elementText = $driver->findElement($this->getSelector($command->getTarget()))->getText(); - $this->assert( - $elementText === $command->getValue(), - sprintf('Actual text "%s" did not match "%s"', $elementText, $command->getValue()) - ); - break; - case self::ASSERT_NOT_TEXT: - $elementText = $driver->findElement($this->getSelector($command->getTarget()))->getText(); - $this->assert( - $elementText !== $command->getValue(), - sprintf('Actual text "%s" did match "%s"', $elementText, $command->getValue()) - ); - break; - case self::ASSERT_VALUE: - $elementValue = $driver->findElement($this->getSelector($command->getTarget()))->getAttribute('value'); - $this->assert( - $elementValue === $command->getValue(), - sprintf('Actual value "%s" did not match "%s"', $elementValue, $command->getValue()) - ); - break; - case self::ASSERT_EDITABLE: - $this->assert( - $this->isElementEditable($driver, $driver->findElement($this->getSelector($command->getTarget()))), - sprintf('Element "%s" is not editable', $command->getTarget()) - ); - break; - case self::ASSERT_NOT_EDITABLE: - $this->assert( - !$this->isElementEditable($driver, $driver->findElement($this->getSelector($command->getTarget()))), - sprintf('Element "%s" is editable', $command->getTarget()) - ); - break; - case self::ASSERT_ELEMENT_PRESENT: - $this->assert( - count($driver->findElements($this->getSelector($command->getTarget()))) > 0, - sprintf('Expected element "%s" was not found in page', $command->getTarget()) - ); - break; - case self::ASSERT_ELEMENT_NOT_PRESENT: - $this->assert( - 0 === count($driver->findElements($this->getSelector($command->getTarget()))), - sprintf('Unexpected element "%s" was found in page', $command->getTarget()) - ); - break; - case self::ASSERT_CHECKED: - $this->assert( - $driver->findElement($this->getSelector($command->getTarget()))->isSelected(), - sprintf('Element "%s" is not checked, expected to be checked', $command->getTarget()) - ); - break; - case self::ASSERT_NOT_CHECKED: - $this->assert( - !$driver->findElement($this->getSelector($command->getTarget()))->isSelected(), - sprintf('Element "%s" is checked, expected to be unchecked', $command->getTarget()) - ); - break; - case self::ASSERT_SELECTED_VALUE: - $select = $this->getSelect($driver->findElement($this->getSelector($command->getTarget()))); - $elementValue = $select->getFirstSelectedOption()->getAttribute('value'); - $this->assert( - $elementValue === $command->getValue(), - sprintf('Actual value "%s" did not match "%s"', $elementValue, $command->getValue()) - ); - break; - case self::ASSERT_NOT_SELECTED_VALUE: - $select = $this->getSelect($driver->findElement($this->getSelector($command->getTarget()))); - $elementValue = $select->getFirstSelectedOption()->getAttribute('value'); - $this->assert( - $elementValue !== $command->getValue(), - sprintf('Actual value "%s" did match "%s"', $elementValue, $command->getValue()) - ); - break; - case self::ASSERT_SELECTED_LABEL: - $select = $this->getSelect($driver->findElement($this->getSelector($command->getTarget()))); - $elementLabel = $select->getFirstSelectedOption()->getText(); - $this->assert( - $elementLabel === $command->getValue(), - sprintf('Actual label "%s" did not match "%s"', $elementLabel, $command->getValue()) - ); - break; - case self::ASSERT_NOT_SELECTED_LABEL: - $select = $this->getSelect($driver->findElement($this->getSelector($command->getTarget()))); - $elementLabel = $select->getFirstSelectedOption()->getText(); - $this->assert( - $elementLabel !== $command->getValue(), - sprintf('Actual label "%s" did match "%s"', $elementLabel, $command->getValue()) - ); - break; - default: - break; - } - } - - public function validateTarget(CommandInterface $command): bool - { - switch ($command->getCommand()) { - case self::ASSERT_TEXT: - case self::ASSERT_NOT_TEXT: - case self::ASSERT_VALUE: - case self::ASSERT_EDITABLE: - case self::ASSERT_NOT_EDITABLE: - case self::ASSERT_ELEMENT_PRESENT: - case self::ASSERT_ELEMENT_NOT_PRESENT: - case self::ASSERT_CHECKED: - case self::ASSERT_NOT_CHECKED: - case self::ASSERT_SELECTED_VALUE: - case self::ASSERT_NOT_SELECTED_VALUE: - case self::ASSERT_SELECTED_LABEL: - case self::ASSERT_NOT_SELECTED_LABEL: - return $command->getTarget() && $this->isValidSelector($command->getTarget()); - default: - return true; - } - } - - protected function assert(bool $assertion, string $message): void - { - if (!$assertion) { - throw new Exception($message); - } - } -} diff --git a/src/Command/Runner/CustomCommandRunner.php b/src/Command/Runner/CustomCommandRunner.php deleted file mode 100644 index 5b45c39f..00000000 --- a/src/Command/Runner/CustomCommandRunner.php +++ /dev/null @@ -1,135 +0,0 @@ -uploadDir = $uploadDir; - } - - public function setWebdriverUri(string $webdriverUri): void - { - $this->webdriverUri = $webdriverUri; - } - - public function getAllCommands(): array - { - return [ - self::UPLOAD, - self::ASSERT_FILE_DOWNLOADED, - self::ASSERT_CLIPBOARD, - self::UPDATE_CLIPBOARD, - ]; - } - - public function getCommandsRequireTarget(): array - { - return $this->getAllCommands(); - } - - public function getCommandsRequireValue(): array - { - return [ - self::UPLOAD, - ]; - } - - public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void - { - switch ($command->getCommand()) { - case self::UPLOAD: - $driver - ->findElement($this->getSelector($command->getTarget())) - ->setFileDetector(new LocalFileDetector()) - ->sendKeys($this->getFilePath($command)); - break; - case self::ASSERT_FILE_DOWNLOADED: - try { - $code = $this->httpClient->request( - 'GET', - sprintf('%s/%s', $this->getUrl('download', $driver), $command->getTarget()) - )->getStatusCode(); - if (200 !== $code) { - throw new Exception(sprintf( - 'Failed expecting that file %s is downloaded', - $command->getTarget() - )); - } - } catch (ExceptionInterface $e) { - throw new RuntimeException(sprintf( - 'Can not get downloaded file %s: %s', - $command->getTarget(), - $e->getMessage() - )); - } - break; - case self::ASSERT_CLIPBOARD: - try { - $clipboard = $this->httpClient->request('GET', $this->getUrl('clipboard', $driver))->getContent(); - if ($command->getTarget() !== $clipboard) { - throw new Exception(sprintf( - "Failed expecting that clipboard's content equals '%s', actual value '%s'", - $command->getTarget(), - $clipboard - )); - } - } catch (ExceptionInterface $e) { - throw new RuntimeException(sprintf( - 'Can not get clipboard: %s', - $e->getMessage() - )); - } - break; - case self::UPDATE_CLIPBOARD: - try { - $this->httpClient->request( - 'POST', - $this->getUrl('clipboard', $driver), - ['body' => $command->getTarget()] - )->getStatusCode(); - } catch (ExceptionInterface $e) { - throw new RuntimeException(sprintf( - 'Can not update clipboard: %s', - $e->getMessage() - )); - } - break; - default: - break; - } - } - - protected function getFilePath(CommandInterface $command): string - { - return $this->uploadDir . DIRECTORY_SEPARATOR . (string) $command->getValue(); - } - - protected function getUrl(string $type, RemoteWebDriver $driver): string - { - return sprintf('%s/%s/%s', rtrim($this->webdriverUri, '/'), $type, $driver->getSessionID()); - } -} diff --git a/src/Command/Runner/KeyboardCommandRunner.php b/src/Command/Runner/KeyboardCommandRunner.php deleted file mode 100644 index 4ffb072a..00000000 --- a/src/Command/Runner/KeyboardCommandRunner.php +++ /dev/null @@ -1,61 +0,0 @@ -getAllCommands(); - } - - public function getCommandsRequireValue(): array - { - return []; - } - - public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void - { - switch ($command->getCommand()) { - case self::TYPE: - $driver - ->findElement($this->getSelector($command->getTarget())) - ->click() - ->clear() - ->sendKeys($this->sanitizeValue($command)); - break; - case self::SEND_KEYS: - $driver - ->findElement($this->getSelector($command->getTarget())) - ->click() - ->sendKeys($this->sanitizeValue($command)); - break; - default: - break; - } - } - - /** - * Don't allow to upload local file. - */ - protected function sanitizeValue(CommandInterface $command): array - { - return [(string) $command->getValue()]; - } -} diff --git a/src/Command/Runner/MouseCommandRunner.php b/src/Command/Runner/MouseCommandRunner.php deleted file mode 100644 index 5ac4be08..00000000 --- a/src/Command/Runner/MouseCommandRunner.php +++ /dev/null @@ -1,228 +0,0 @@ -getAllCommands(); - } - - public function getCommandsRequireValue(): array - { - return [ - self::ADD_SELECTION, - self::REMOVE_SELECTION, - self::CLICK_AT, - self::DOUBLE_CLICK_AT, - self::DRAG_AND_DROP_TO_OBJECT, - self::MOUSE_DOWN_AT, - self::MOUSE_MOVE_AT, - self::MOUSE_UP_AT, - self::SELECT, - ]; - } - - public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void - { - switch ($command->getCommand()) { - case self::ADD_SELECTION: - $select = $this->getSelect($driver->findElement($this->getSelector($command->getTarget()))); - if (str_starts_with($command->getValue(), 'index=')) { - $select->selectByIndex((int) substr($command->getValue(), 6)); - } elseif (str_starts_with($command->getValue(), 'value=')) { - $select->selectByValue(substr($command->getValue(), 6)); - } elseif (str_starts_with($command->getValue(), 'label=')) { - $select->selectByVisibleText(substr($command->getValue(), 6)); - } - break; - case self::REMOVE_SELECTION: - $select = $this->getSelect($driver->findElement($this->getSelector($command->getTarget()))); - if (str_starts_with($command->getValue(), 'index=')) { - $select->deselectByIndex((int) substr($command->getValue(), 6)); - } elseif (str_starts_with($command->getValue(), 'value=')) { - $select->deselectByValue(substr($command->getValue(), 6)); - } elseif (str_starts_with($command->getValue(), 'label=')) { - $select->deselectByVisibleText(substr($command->getValue(), 6)); - } - break; - case self::CHECK: - $element = $driver->findElement($this->getSelector($command->getTarget())); - if (!$element->isSelected()) { - $element->click(); - } - break; - case self::UNCHECK: - $element = $driver->findElement($this->getSelector($command->getTarget())); - if ($element->isSelected()) { - $element->click(); - } - break; - case self::CLICK: - $driver->findElement($this->getSelector($command->getTarget()))->click(); - break; - case self::CLICK_AT: - $point = $this->getPoint($command->getValue()); - $driver->action()->moveToElement( - $driver->findElement($this->getSelector($command->getTarget())), - $point->getX(), - $point->getY() - )->click()->perform(); - break; - case self::DOUBLE_CLICK: - $driver->action()->doubleClick( - $driver->findElement($this->getSelector($command->getTarget())) - )->perform(); - break; - case self::DOUBLE_CLICK_AT: - $point = $this->getPoint($command->getValue()); - $driver->action()->moveToElement( - $driver->findElement($this->getSelector($command->getTarget())), - $point->getX(), - $point->getY() - )->doubleClick()->perform(); - break; - case self::DRAG_AND_DROP_TO_OBJECT: - $driver->action()->dragAndDrop( - $driver->findElement($this->getSelector($command->getTarget())), - $driver->findElement($this->getSelector($command->getValue())) - )->perform(); - break; - case self::MOUSE_DOWN: - $driver->getMouse()->mouseDown( - $driver->findElement($this->getSelector($command->getTarget()))->getCoordinates() - ); - break; - case self::MOUSE_DOWN_AT: - $point = $this->getPoint($command->getValue()); - $driver->getMouse()->mouseMove( - $driver->findElement($this->getSelector($command->getTarget()))->getCoordinates(), - $point->getX(), - $point->getY() - )->mouseDown(); - break; - case self::MOUSE_MOVE_AT: - $point = $this->getPoint($command->getValue()); - $driver->getMouse()->mouseMove( - $driver->findElement($this->getSelector($command->getTarget()))->getCoordinates(), - $point->getX(), - $point->getY() - ); - break; - case self::MOUSE_OUT: - $element = $driver->findElement($this->getSelector($command->getTarget())); - [$rect, $vp] = $driver->executeScript( - // phpcs:ignore Generic.Files.LineLength - 'return [arguments[0].getBoundingClientRect(), {height: window.innerHeight, width: window.innerWidth}];', - [$element] - ); - if ($rect->top > 0) { - // try top - $y = -($rect->height / 2 + 1); - $driver->getMouse()->mouseMove($element->getCoordinates(), null, $y); - break; - } elseif ($vp->width > $rect->right) { - // try right - $x = $rect->right / 2 + 1; - $driver->getMouse()->mouseMove($element->getCoordinates(), $x); - break; - } elseif ($vp->height > $rect->bottom) { - // try bottom - $y = $rect->height / 2 + 1; - $driver->getMouse()->mouseMove($element->getCoordinates(), null, $y); - break; - } elseif ($rect->left > 0) { - // try left - $x = (int) (-($rect->right / 2)); - $driver->getMouse()->mouseMove($element->getCoordinates(), $x); - break; - } - - throw new Exception( - 'Unable to perform mouse out as the element takes up the entire viewport' - ); - case self::MOUSE_OVER: - $driver->getMouse()->mouseMove( - $driver->findElement($this->getSelector($command->getTarget()))->getCoordinates() - ); - break; - case self::MOUSE_UP: - $driver->getMouse()->mouseUp( - $driver->findElement($this->getSelector($command->getTarget()))->getCoordinates() - ); - break; - case self::MOUSE_UP_AT: - $point = $this->getPoint($command->getValue()); - $driver->getMouse()->mouseMove( - $driver->findElement($this->getSelector($command->getTarget()))->getCoordinates(), - $point->getX(), - $point->getY() - )->mouseUp(); - break; - case self::SELECT: - $driver - ->findElement($this->getSelector($command->getTarget())) - ->findElement($this->getSelector($command->getValue())) - ->click(); - break; - default: - break; - } - } - - protected function getPoint(string $target): WebDriverPoint - { - list($x, $y) = explode(',', $target); - - return new WebDriverPoint((int) $x, (int) $y); - } -} diff --git a/src/Command/Runner/ScriptCommandRunner.php b/src/Command/Runner/ScriptCommandRunner.php deleted file mode 100644 index 7dda669a..00000000 --- a/src/Command/Runner/ScriptCommandRunner.php +++ /dev/null @@ -1,65 +0,0 @@ -getAllCommands(); - } - - public function getCommandsRequireValue(): array - { - return [ - self::EXECUTE_SCRIPT, - self::EXECUTE_ASYNC_SCRIPT, - ]; - } - - public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void - { - switch ($command->getCommand()) { - case self::RUN_SCRIPT: - $driver->executeScript($command->getTarget()); - break; - case self::EXECUTE_SCRIPT: - $value = $driver->executeScript($command->getTarget()); - if ($command->getValue()) { - $values->setValue($command->getValue(), $value); - } - break; - case self::EXECUTE_ASYNC_SCRIPT: - $value = $driver->executeAsyncScript($command->getTarget()); - if ($command->getValue()) { - $values->setValue($command->getValue(), $value); - } - break; - default: - break; - } - } - - public function validateTarget(CommandInterface $command): bool - { - return true; - } -} diff --git a/src/Command/Runner/StoreCommandRunner.php b/src/Command/Runner/StoreCommandRunner.php deleted file mode 100644 index 6aaa07ca..00000000 --- a/src/Command/Runner/StoreCommandRunner.php +++ /dev/null @@ -1,138 +0,0 @@ -getCommand()) { - case self::STORE: - $values->setValue($command->getValue(), $command->getTarget()); - break; - case self::STORE_ATTRIBUTE: - list($elementLocator, $attributeName) = explode('@', $command->getTarget(), 2); - $values->setValue( - $command->getValue(), - $driver->findElement($this->getSelector($elementLocator))->getAttribute($attributeName) - ); - break; - case self::STORE_ELEMENT_COUNT: - $values->setValue( - $command->getValue(), - count($driver->findElements($this->getSelector($command->getTarget()))) - ); - break; - case self::STORE_JSON: - $values->setValue( - $command->getValue(), - json_decode($command->getTarget()) - ); - break; - case self::STORE_TEXT: - $values->setValue( - $command->getValue(), - $driver->findElement($this->getSelector($command->getTarget()))->getText() - ); - break; - case self::STORE_TITLE: - $values->setValue($command->getTarget(), $driver->getTitle()); - break; - case self::STORE_VALUE: - $values->setValue( - $command->getValue(), - $driver->findElement($this->getSelector($command->getTarget()))->getAttribute('value') - ); - break; - case self::STORE_WINDOW_HANDLE: - $values->setValue($command->getTarget(), $driver->getWindowHandle()); - break; - default: - break; - } - } - - public function validateTarget(CommandInterface $command): bool - { - switch ($command->getCommand()) { - case self::STORE_ATTRIBUTE: - return $command->getTarget() && $this->isValidAttribute($command->getTarget()); - case self::STORE_ELEMENT_COUNT: - case self::STORE_TEXT: - case self::STORE_VALUE: - return $command->getTarget() && $this->isValidSelector($command->getTarget()); - case self::STORE_JSON: - return $command->getTarget() && $this->isValidJson($command->getTarget()); - default: - return true; - } - } - - protected function isValidAttribute(string $target): bool - { - list($elementLocator) = explode('@', $target, 2); - - return $this->isValidSelector($elementLocator); - } - - protected function isValidJson(string $target): bool - { - json_decode($target); - - return JSON_ERROR_NONE === json_last_error(); - } -} diff --git a/src/Command/Runner/WaitCommandRunner.php b/src/Command/Runner/WaitCommandRunner.php deleted file mode 100644 index 8ac874f0..00000000 --- a/src/Command/Runner/WaitCommandRunner.php +++ /dev/null @@ -1,90 +0,0 @@ -getAllCommands(); - } - - public function getCommandsRequireValue(): array - { - return $this->getAllCommands(); - } - - public function run(CommandInterface $command, ValuesInterface $values, RemoteWebDriver $driver): void - { - switch ($command->getCommand()) { - case self::WAIT_FOR_ELEMENT_EDITABLE: - $driver->wait((int) $command->getValue())->until( - fn () => $this->isElementEditable( - $driver, - $driver->findElement($this->getSelector($command->getTarget())) - ), - 'Timed out waiting for element to be editable' - ); - break; - case self::WAIT_FOR_ELEMENT_NOT_EDITABLE: - $driver->wait((int) $command->getValue())->until( - fn () => !$this->isElementEditable( - $driver, - $driver->findElement($this->getSelector($command->getTarget())) - ), - 'Timed out waiting for element to not be editable' - ); - break; - case self::WAIT_FOR_ELEMENT_PRESENT: - $driver->wait((int) $command->getValue())->until( - WebDriverExpectedCondition::presenceOfElementLocated($this->getSelector($command->getTarget())) - ); - break; - case self::WAIT_FOR_ELEMENT_NOT_PRESENT: - $elements = $driver->findElements($this->getSelector($command->getTarget())); - if (count($elements) > 0) { - $driver->wait((int) $command->getValue())->until( - WebDriverExpectedCondition::stalenessOf($elements[0]) - ); - } - break; - case self::WAIT_FOR_ELEMENT_VISIBLE: - $driver->wait((int) $command->getValue())->until( - WebDriverExpectedCondition::visibilityOfElementLocated($this->getSelector($command->getTarget())) - ); - break; - case self::WAIT_FOR_ELEMENT_NOT_VISIBLE: - $driver->wait((int) $command->getValue())->until( - WebDriverExpectedCondition::invisibilityOfElementLocated($this->getSelector($command->getTarget())) - ); - break; - default: - break; - } - } -} diff --git a/src/Command/Runner/WindowCommandRunner.php b/src/Command/Runner/WindowCommandRunner.php deleted file mode 100644 index dc83fa55..00000000 --- a/src/Command/Runner/WindowCommandRunner.php +++ /dev/null @@ -1,138 +0,0 @@ -getCommand()) { - case self::OPEN: - $driver->get($command->getTarget()); - break; - case self::SET_WINDOW_SIZE: - $driver->manage()->window()->setSize($this->getDimension($command->getTarget())); - break; - case self::SELECT_WINDOW: - $driver->switchTo()->window($this->getHandle($command->getTarget())); - break; - case self::CLOSE: - $driver->close(); - break; - case self::SELECT_FRAME: - $targetLocator = $driver->switchTo(); - if ('relative=top' === $command->getTarget()) { - $targetLocator->defaultContent(); - } elseif ('relative=parent' === $command->getTarget()) { - $targetLocator->parent(); - } elseif (str_starts_with($command->getTarget(), 'index=')) { - $targetLocator->frame((int) substr($command->getTarget(), 6)); - } else { - $webDriverBy = $this->getSelector($command->getTarget()); - $driver->wait()->until( - WebDriverExpectedCondition::presenceOfElementLocated($webDriverBy) - ); - $targetLocator->frame($driver->findElement($webDriverBy)); - } - break; - default: - break; - } - } - - public function validateTarget(CommandInterface $command): bool - { - switch ($command->getCommand()) { - case self::OPEN: - return $command->getTarget() && $this->isValidUrl($command->getTarget()); - case self::SET_WINDOW_SIZE: - return $command->getTarget() && 2 === count(explode('x', $command->getTarget())); - case self::SELECT_WINDOW: - return $command->getTarget() && $this->isValidHandle($command->getTarget()); - case self::SELECT_FRAME: - return $command->getTarget() && $this->isValidFrame($command->getTarget()); - default: - return true; - } - } - - protected function getDimension(string $target): WebDriverDimension - { - list($width, $height) = explode('x', $target); - - return new WebDriverDimension((int) $width, (int) $height); - } - - protected function getHandle(string $target): string - { - if (!$this->isValidHandle($target)) { - throw new Exception('Invalid window handle given (e.g. handle=${handleVariable})'); - } - - return substr($target, 7); - } - - protected function isValidHandle(string $target): bool - { - return str_starts_with($target, 'handle='); - } - - protected function isValidFrame(string $target): bool - { - return $target && ( - in_array($target, ['relative=top', 'relative=parent']) - || str_starts_with($target, 'index=') - || $this->isValidSelector($target) - ); - } - - /** - * TODO Find a solution better than this. - */ - protected function isValidUrl(string $target): bool - { - $url = filter_var($target, FILTER_SANITIZE_URL); - - return filter_var($url, FILTER_VALIDATE_URL); - } -} diff --git a/src/Command/Script/AbstractScriptCommand.php b/src/Command/Script/AbstractScriptCommand.php new file mode 100644 index 00000000..950558d8 --- /dev/null +++ b/src/Command/Script/AbstractScriptCommand.php @@ -0,0 +1,18 @@ +executeAsyncScript($target); + if ($value) { + $values->setValue($value, $result); + } + } +} diff --git a/src/Command/Script/ExecuteScriptCommand.php b/src/Command/Script/ExecuteScriptCommand.php new file mode 100644 index 00000000..1ef25fa4 --- /dev/null +++ b/src/Command/Script/ExecuteScriptCommand.php @@ -0,0 +1,38 @@ +executeScript($target); + if ($value) { + $values->setValue($value, $result); + } + } +} diff --git a/src/Command/Script/RunScriptCommand.php b/src/Command/Script/RunScriptCommand.php new file mode 100644 index 00000000..d251407b --- /dev/null +++ b/src/Command/Script/RunScriptCommand.php @@ -0,0 +1,30 @@ +executeScript($target); + } +} diff --git a/src/Command/Store/AbstractStoreCommand.php b/src/Command/Store/AbstractStoreCommand.php new file mode 100644 index 00000000..a6235f2a --- /dev/null +++ b/src/Command/Store/AbstractStoreCommand.php @@ -0,0 +1,13 @@ +setValue( + $value, + $driver->findElement($this->getSelector($elementLocator))->getAttribute($attributeName) + ); + } +} diff --git a/src/Command/Store/StoreCommand.php b/src/Command/Store/StoreCommand.php new file mode 100644 index 00000000..b16ef627 --- /dev/null +++ b/src/Command/Store/StoreCommand.php @@ -0,0 +1,45 @@ +setValue($value, $target); + } +} diff --git a/src/Command/Store/StoreElementCountCommand.php b/src/Command/Store/StoreElementCountCommand.php new file mode 100644 index 00000000..cbb29196 --- /dev/null +++ b/src/Command/Store/StoreElementCountCommand.php @@ -0,0 +1,38 @@ +setValue( + $value, + count($driver->findElements($this->getSelector($target))) + ); + } +} diff --git a/src/Command/Store/StoreJsonCommand.php b/src/Command/Store/StoreJsonCommand.php new file mode 100644 index 00000000..b682d7d6 --- /dev/null +++ b/src/Command/Store/StoreJsonCommand.php @@ -0,0 +1,52 @@ +setValue($value, json_decode($target)); + } +} diff --git a/src/Command/Store/StoreTextCommand.php b/src/Command/Store/StoreTextCommand.php new file mode 100644 index 00000000..620366b1 --- /dev/null +++ b/src/Command/Store/StoreTextCommand.php @@ -0,0 +1,38 @@ +setValue( + $value, + $driver->findElement($this->getSelector($target))->getText() + ); + } +} diff --git a/src/Command/Store/StoreTitleCommand.php b/src/Command/Store/StoreTitleCommand.php new file mode 100644 index 00000000..4e40e557 --- /dev/null +++ b/src/Command/Store/StoreTitleCommand.php @@ -0,0 +1,35 @@ +setValue($target, $driver->getTitle()); + } +} diff --git a/src/Command/Store/StoreValueCommand.php b/src/Command/Store/StoreValueCommand.php new file mode 100644 index 00000000..0bfe1eba --- /dev/null +++ b/src/Command/Store/StoreValueCommand.php @@ -0,0 +1,38 @@ +setValue( + $value, + $driver->findElement($this->getSelector($target))->getAttribute('value') + ); + } +} diff --git a/src/Command/Store/StoreWindowHandleCommand.php b/src/Command/Store/StoreWindowHandleCommand.php new file mode 100644 index 00000000..c7a64fb4 --- /dev/null +++ b/src/Command/Store/StoreWindowHandleCommand.php @@ -0,0 +1,35 @@ +setValue($target, $driver->getWindowHandle()); + } +} diff --git a/src/Command/Wait/AbstractWaitCommand.php b/src/Command/Wait/AbstractWaitCommand.php new file mode 100644 index 00000000..e3e8f925 --- /dev/null +++ b/src/Command/Wait/AbstractWaitCommand.php @@ -0,0 +1,33 @@ +wait((int) $value)->until( + fn () => $this->isElementEditable( + $driver, + $driver->findElement($this->getSelector($target)) + ), + 'Timed out waiting for element to be editable' + ); + } +} diff --git a/src/Command/Wait/WaitForElementNotEditableCommand.php b/src/Command/Wait/WaitForElementNotEditableCommand.php new file mode 100644 index 00000000..8a5859fb --- /dev/null +++ b/src/Command/Wait/WaitForElementNotEditableCommand.php @@ -0,0 +1,21 @@ +wait((int) $value)->until( + fn () => !$this->isElementEditable( + $driver, + $driver->findElement($this->getSelector($target)) + ), + 'Timed out waiting for element to not be editable' + ); + } +} diff --git a/src/Command/Wait/WaitForElementNotPresentCommand.php b/src/Command/Wait/WaitForElementNotPresentCommand.php new file mode 100644 index 00000000..c05306b9 --- /dev/null +++ b/src/Command/Wait/WaitForElementNotPresentCommand.php @@ -0,0 +1,21 @@ +findElements($this->getSelector($target)); + if (count($elements) > 0) { + $driver->wait((int) $value)->until( + WebDriverExpectedCondition::stalenessOf($elements[0]) + ); + } + } +} diff --git a/src/Command/Wait/WaitForElementNotVisibleCommand.php b/src/Command/Wait/WaitForElementNotVisibleCommand.php new file mode 100644 index 00000000..4cab1f77 --- /dev/null +++ b/src/Command/Wait/WaitForElementNotVisibleCommand.php @@ -0,0 +1,18 @@ +wait((int) $value)->until( + WebDriverExpectedCondition::invisibilityOfElementLocated($this->getSelector($target)) + ); + } +} diff --git a/src/Command/Wait/WaitForElementPresentCommand.php b/src/Command/Wait/WaitForElementPresentCommand.php new file mode 100644 index 00000000..0f18f7c3 --- /dev/null +++ b/src/Command/Wait/WaitForElementPresentCommand.php @@ -0,0 +1,18 @@ +wait((int) $value)->until( + WebDriverExpectedCondition::presenceOfElementLocated($this->getSelector($target)) + ); + } +} diff --git a/src/Command/Wait/WaitForElementVisibleCommand.php b/src/Command/Wait/WaitForElementVisibleCommand.php new file mode 100644 index 00000000..cdb1de22 --- /dev/null +++ b/src/Command/Wait/WaitForElementVisibleCommand.php @@ -0,0 +1,18 @@ +wait((int) $value)->until( + WebDriverExpectedCondition::visibilityOfElementLocated($this->getSelector($target)) + ); + } +} diff --git a/src/Command/Window/AbstractWindowCommand.php b/src/Command/Window/AbstractWindowCommand.php new file mode 100644 index 00000000..a7fa4de7 --- /dev/null +++ b/src/Command/Window/AbstractWindowCommand.php @@ -0,0 +1,18 @@ +close(); + } +} diff --git a/src/Command/Window/OpenCommand.php b/src/Command/Window/OpenCommand.php new file mode 100644 index 00000000..02bee25b --- /dev/null +++ b/src/Command/Window/OpenCommand.php @@ -0,0 +1,40 @@ +get($target); + } +} diff --git a/src/Command/Window/SelectFrameCommand.php b/src/Command/Window/SelectFrameCommand.php new file mode 100644 index 00000000..c02d48ec --- /dev/null +++ b/src/Command/Window/SelectFrameCommand.php @@ -0,0 +1,53 @@ +switchTo(); + if ('relative=top' === $target) { + $targetLocator->defaultContent(); + } elseif ('relative=parent' === $target) { + $targetLocator->parent(); + } elseif (str_starts_with($target, 'index=')) { + $targetLocator->frame((int) substr($target, 6)); + } else { + $webDriverBy = $this->getSelector($target); + $driver->wait()->until( + WebDriverExpectedCondition::presenceOfElementLocated($webDriverBy) + ); + $targetLocator->frame($driver->findElement($webDriverBy)); + } + } +} diff --git a/src/Command/Window/SelectWindowCommand.php b/src/Command/Window/SelectWindowCommand.php new file mode 100644 index 00000000..7fd048a5 --- /dev/null +++ b/src/Command/Window/SelectWindowCommand.php @@ -0,0 +1,40 @@ +switchTo()->window($this->getHandle($target)); + } +} diff --git a/src/Command/Window/SetWindowSizeCommand.php b/src/Command/Window/SetWindowSizeCommand.php new file mode 100644 index 00000000..952f6351 --- /dev/null +++ b/src/Command/Window/SetWindowSizeCommand.php @@ -0,0 +1,43 @@ +manage()->window()->setSize($this->getDimension($target)); + } +} diff --git a/src/Service/Step/Runner/StepRunner.php b/src/Service/Step/Runner/StepRunner.php index 6b76c602..73c5c4c5 100644 --- a/src/Service/Step/Runner/StepRunner.php +++ b/src/Service/Step/Runner/StepRunner.php @@ -3,7 +3,7 @@ namespace Tienvx\Bundle\MbtBundle\Service\Step\Runner; use Facebook\WebDriver\Remote\RemoteWebDriver; -use Tienvx\Bundle\MbtBundle\Command\CommandRunnerManagerInterface; +use Tienvx\Bundle\MbtBundle\Command\CommandManagerInterface; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\PlaceInterface; @@ -13,11 +13,11 @@ class StepRunner implements StepRunnerInterface { - protected CommandRunnerManagerInterface $commandRunnerManager; + protected CommandManagerInterface $commandManager; - public function __construct(CommandRunnerManagerInterface $commandRunnerManager) + public function __construct(CommandManagerInterface $commandManager) { - $this->commandRunnerManager = $commandRunnerManager; + $this->commandManager = $commandManager; } public function run(StepInterface $step, RevisionInterface $revision, RemoteWebDriver $driver): void @@ -39,7 +39,13 @@ protected function runCommands(array $commands, RemoteWebDriver $driver): void $values = new Values(); foreach ($commands as $command) { if ($command instanceof CommandInterface) { - $this->commandRunnerManager->run($command, $values, $driver); + $this->commandManager->run( + $command->getCommand(), + $command->getTarget(), + $command->getValue(), + $values, + $driver + ); } } } diff --git a/src/TienvxMbtBundle.php b/src/TienvxMbtBundle.php index 37c4cf5b..04b9dfaf 100644 --- a/src/TienvxMbtBundle.php +++ b/src/TienvxMbtBundle.php @@ -6,7 +6,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\HttpKernel\Bundle\AbstractBundle; -use Tienvx\Bundle\MbtBundle\Command\Runner\CustomCommandRunner; +use Tienvx\Bundle\MbtBundle\Command\CommandManagerInterface; use Tienvx\Bundle\MbtBundle\DependencyInjection\Compiler\PluginPass; use Tienvx\Bundle\MbtBundle\Service\SelenoidHelperInterface; @@ -34,8 +34,7 @@ public function loadExtension(array $config, ContainerConfigurator $container, C ->call('setWebdriverUri', [$config[static::WEBDRIVER_URI]]) ; $container->services() - ->get(CustomCommandRunner::class) - ->call('setWebdriverUri', [$config[static::WEBDRIVER_URI]]) + ->get(CommandManagerInterface::class) ->call('setUploadDir', [$config[static::UPLOAD_DIR]]) ; } diff --git a/src/Validator/ValidCommand.php b/src/Validator/ValidCommand.php index 47c5d0f6..fd519080 100644 --- a/src/Validator/ValidCommand.php +++ b/src/Validator/ValidCommand.php @@ -13,10 +13,11 @@ class ValidCommand extends Constraint self::IS_COMMAND_INVALID_ERROR => 'IS_COMMAND_INVALID_ERROR', ]; - public string $commandMessage = 'mbt.model.command.invalid_command'; + public string $invalidCommandMessage = 'mbt.model.command.invalid_command'; public string $targetRequiredMessage = 'mbt.model.command.required_target'; public string $targetInvalidMessage = 'mbt.model.command.invalid_target'; public string $valueRequiredMessage = 'mbt.model.command.required_value'; + public string $valueInvalidMessage = 'mbt.model.command.invalid_value'; public function getTargets(): string|array { diff --git a/src/Validator/ValidCommandValidator.php b/src/Validator/ValidCommandValidator.php index 6f178871..734c7e86 100644 --- a/src/Validator/ValidCommandValidator.php +++ b/src/Validator/ValidCommandValidator.php @@ -6,16 +6,13 @@ use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Exception\UnexpectedValueException; -use Tienvx\Bundle\MbtBundle\Command\CommandRunnerManagerInterface; +use Tienvx\Bundle\MbtBundle\Command\CommandManagerInterface; use Tienvx\Bundle\MbtBundle\ValueObject\Model\Command; class ValidCommandValidator extends ConstraintValidator { - protected CommandRunnerManagerInterface $commandRunnerManager; - - public function __construct(CommandRunnerManagerInterface $commandRunnerManager) + public function __construct(protected CommandManagerInterface $commandManager) { - $this->commandRunnerManager = $commandRunnerManager; } public function validate($value, Constraint $constraint) @@ -32,35 +29,35 @@ public function validate($value, Constraint $constraint) throw new UnexpectedValueException($value, Command::class); } - if (!in_array($value->getCommand(), $this->commandRunnerManager->getAllCommands())) { - $this->context->buildViolation($constraint->commandMessage) + if (!$this->commandManager->hasCommand($value->getCommand())) { + $this->context->buildViolation($constraint->invalidCommandMessage) ->setCode(ValidCommand::IS_COMMAND_INVALID_ERROR) ->atPath('command') ->addViolation(); } - if (in_array($value->getCommand(), $this->commandRunnerManager->getCommandsRequireTarget())) { - if (is_null($value->getTarget())) { - $this->context->buildViolation($constraint->targetRequiredMessage) - ->setCode(ValidCommand::IS_COMMAND_INVALID_ERROR) - ->atPath('target') - ->addViolation(); - } elseif (!$this->commandRunnerManager->validateTarget($value)) { - $this->context->buildViolation($constraint->targetInvalidMessage) - ->setCode(ValidCommand::IS_COMMAND_INVALID_ERROR) - ->atPath('target') - ->addViolation(); - } + if ($this->commandManager->isTargetMissing($value->getCommand(), $value->getTarget())) { + $this->context->buildViolation($constraint->targetRequiredMessage) + ->setCode(ValidCommand::IS_COMMAND_INVALID_ERROR) + ->atPath('target') + ->addViolation(); + } elseif ($this->commandManager->isTargetNotValid($value->getCommand(), $value->getTarget())) { + $this->context->buildViolation($constraint->targetInvalidMessage) + ->setCode(ValidCommand::IS_COMMAND_INVALID_ERROR) + ->atPath('target') + ->addViolation(); } - if ( - in_array($value->getCommand(), $this->commandRunnerManager->getCommandsRequireValue()) - && is_null($value->getValue()) - ) { + if ($this->commandManager->isValueMissing($value->getCommand(), $value->getValue())) { $this->context->buildViolation($constraint->valueRequiredMessage) ->setCode(ValidCommand::IS_COMMAND_INVALID_ERROR) ->atPath('value') ->addViolation(); + } elseif ($this->commandManager->isValueNotValid($value->getCommand(), $value->getValue())) { + $this->context->buildViolation($constraint->valueInvalidMessage) + ->setCode(ValidCommand::IS_COMMAND_INVALID_ERROR) + ->atPath('value') + ->addViolation(); } } } diff --git a/tests/Command/Alert/AcceptAlertCommandTest.php b/tests/Command/Alert/AcceptAlertCommandTest.php new file mode 100644 index 00000000..94d12a56 --- /dev/null +++ b/tests/Command/Alert/AcceptAlertCommandTest.php @@ -0,0 +1,55 @@ +createMock(WebDriverAlert::class); + $alert->expects($this->once())->method('accept'); + $targetLocator = $this->createMock(RemoteTargetLocator::class); + $targetLocator->expects($this->once())->method('alert')->willReturn($alert); + $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); + $this->command->run(null, null, $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Alert/AnswerPromptCommandTest.php b/tests/Command/Alert/AnswerPromptCommandTest.php new file mode 100644 index 00000000..88575404 --- /dev/null +++ b/tests/Command/Alert/AnswerPromptCommandTest.php @@ -0,0 +1,56 @@ +createMock(WebDriverAlert::class); + $alert->expects($this->once())->method('sendKeys')->with('Yes, I agree'); + $alert->expects($this->once())->method('accept'); + $targetLocator = $this->createMock(RemoteTargetLocator::class); + $targetLocator->expects($this->once())->method('alert')->willReturn($alert); + $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); + $this->command->run('Yes, I agree', null, $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', true], + ['anything', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Alert/DismissPromptCommandTest.php b/tests/Command/Alert/DismissPromptCommandTest.php new file mode 100644 index 00000000..3d3c1eed --- /dev/null +++ b/tests/Command/Alert/DismissPromptCommandTest.php @@ -0,0 +1,55 @@ +createMock(WebDriverAlert::class); + $alert->expects($this->once())->method('dismiss'); + $targetLocator = $this->createMock(RemoteTargetLocator::class); + $targetLocator->expects($this->once())->method('alert')->willReturn($alert); + $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); + $this->command->run(null, null, $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Assert/AssertAlertCommandTest.php b/tests/Command/Assert/AssertAlertCommandTest.php new file mode 100644 index 00000000..bd0e3193 --- /dev/null +++ b/tests/Command/Assert/AssertAlertCommandTest.php @@ -0,0 +1,70 @@ +expectExceptionObject($exception); + } + $alert = $this->createMock(WebDriverAlert::class); + $alert->expects($this->once())->method('getText')->willReturn('expected alert'); + $locator = $this->createMock(RemoteTargetLocator::class); + $locator->expects($this->once())->method('alert')->willReturn($alert); + $this->driver->expects($this->once())->method('switchTo')->willReturn($locator); + $this->command->run($target, null, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + ['expected alert', null], + ['unexpected alert', new Exception('Actual alert text "expected alert" did not match "unexpected alert"')], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', true], + ['anything', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Assert/AssertCheckedCommandTest.php b/tests/Command/Assert/AssertCheckedCommandTest.php new file mode 100644 index 00000000..8b49e288 --- /dev/null +++ b/tests/Command/Assert/AssertCheckedCommandTest.php @@ -0,0 +1,77 @@ +expectExceptionObject($exception); + } + $element = $this->createMock(WebDriverElement::class); + $element->expects($this->once())->method('isSelected')->willReturn($isSelected); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'css selector' === $selector->getMechanism() + && '.term-and-condition' === $selector->getValue(); + })) + ->willReturn($element); + $this->command->run('css=.term-and-condition', null, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + [true, null], + [false, new Exception('Element "css=.term-and-condition" is not checked, expected to be checked')], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Assert/AssertCommandTest.php b/tests/Command/Assert/AssertCommandTest.php new file mode 100644 index 00000000..57d3ba8b --- /dev/null +++ b/tests/Command/Assert/AssertCommandTest.php @@ -0,0 +1,69 @@ + 'value1', 'key2' => 'value2']); + if ($exception) { + $this->expectExceptionObject($exception); + } else { + $this->expectNotToPerformAssertions(); + } + $this->command->run($target, $value, $values, $this->driver); + } + + public function runProvider(): array + { + return [ + ['key1', 'value1', null], + ['key2', 'value3', new Exception('Actual value "value2" did not match "value3"')], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Assert/AssertEditableCommandTest.php b/tests/Command/Assert/AssertEditableCommandTest.php new file mode 100644 index 00000000..0c10fd0e --- /dev/null +++ b/tests/Command/Assert/AssertEditableCommandTest.php @@ -0,0 +1,88 @@ +expectExceptionObject($exception); + } + $element = $this->createMock(WebDriverElement::class); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'name' === $selector->getMechanism() + && 'username' === $selector->getValue(); + })) + ->willReturn($element); + $this->driver + ->expects($this->once()) + ->method('executeScript') + ->with( + 'return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', + [$element] + ) + ->willReturn((object) ['enabled' => $enabled, 'readonly' => $readonly]); + $this->command->run('name=username', null, $this->values, $this->driver); + } + + public function runProvider(): array + { + $exception = new Exception('Element "name=username" is not editable'); + + return [ + [true, false, null], + [true, true, $exception], + [false, true, $exception], + [false, false, $exception], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Assert/AssertElementNotPresentCommandTest.php b/tests/Command/Assert/AssertElementNotPresentCommandTest.php new file mode 100644 index 00000000..46676684 --- /dev/null +++ b/tests/Command/Assert/AssertElementNotPresentCommandTest.php @@ -0,0 +1,81 @@ +expectExceptionObject($exception); + } + $element = $this->createMock(WebDriverElement::class); + $this->driver + ->expects($this->once()) + ->method('findElements') + ->with( + $this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'css selector' === $selector->getMechanism() + && '.cart' === $selector->getValue(); + }) + ) + ->willReturn(array_fill(0, $count, $element)); + $this->command->run('css=.cart', null, $this->values, $this->driver); + } + + public function runProvider(): array + { + $exception = new Exception('Unexpected element "css=.cart" was found in page'); + + return [ + [0, null], + [1, $exception], + [2, $exception], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Assert/AssertElementPresentCommandTest.php b/tests/Command/Assert/AssertElementPresentCommandTest.php new file mode 100644 index 00000000..ba71f049 --- /dev/null +++ b/tests/Command/Assert/AssertElementPresentCommandTest.php @@ -0,0 +1,81 @@ +expectExceptionObject($exception); + } + $element = $this->createMock(WebDriverElement::class); + $this->driver + ->expects($this->once()) + ->method('findElements') + ->with( + $this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'css selector' === $selector->getMechanism() + && '.cart' === $selector->getValue(); + }) + ) + ->willReturn(array_fill(0, $count, $element)); + $this->command->run('css=.cart', null, $this->values, $this->driver); + } + + public function runProvider(): array + { + $exception = new Exception('Expected element "css=.cart" was not found in page'); + + return [ + [0, $exception], + [1, null], + [2, null], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Assert/AssertNotCheckedCommandTest.php b/tests/Command/Assert/AssertNotCheckedCommandTest.php new file mode 100644 index 00000000..db5eb899 --- /dev/null +++ b/tests/Command/Assert/AssertNotCheckedCommandTest.php @@ -0,0 +1,77 @@ +expectExceptionObject($exception); + } + $element = $this->createMock(WebDriverElement::class); + $element->expects($this->once())->method('isSelected')->willReturn($isSelected); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'css selector' === $selector->getMechanism() + && '.term-and-condition' === $selector->getValue(); + })) + ->willReturn($element); + $this->command->run('css=.term-and-condition', null, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + [false, null], + [true, new Exception('Element "css=.term-and-condition" is checked, expected to be unchecked')], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Assert/AssertNotEditableCommandTest.php b/tests/Command/Assert/AssertNotEditableCommandTest.php new file mode 100644 index 00000000..f8fc5ce8 --- /dev/null +++ b/tests/Command/Assert/AssertNotEditableCommandTest.php @@ -0,0 +1,88 @@ +expectExceptionObject($exception); + } + $element = $this->createMock(WebDriverElement::class); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'name' === $selector->getMechanism() + && 'username' === $selector->getValue(); + })) + ->willReturn($element); + $this->driver + ->expects($this->once()) + ->method('executeScript') + ->with( + 'return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', + [$element] + ) + ->willReturn((object) ['enabled' => $enabled, 'readonly' => $readonly]); + $this->command->run('name=username', null, $this->values, $this->driver); + } + + public function runProvider(): array + { + $exception = new Exception('Element "name=username" is editable'); + + return [ + [true, false, $exception], + [true, true, null], + [false, true, null], + [false, false, null], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Assert/AssertNotSelectedLabelCommandTest.php b/tests/Command/Assert/AssertNotSelectedLabelCommandTest.php new file mode 100644 index 00000000..ca0a50c8 --- /dev/null +++ b/tests/Command/Assert/AssertNotSelectedLabelCommandTest.php @@ -0,0 +1,83 @@ +expectExceptionObject($exception); + } + $element = $this->createMock(WebDriverElement::class); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'partial link text' === $selector->getMechanism() + && 'Language' === $selector->getValue(); + })) + ->willReturn($element); + $option = $this->createMock(WebDriverElement::class); + $option->expects($this->once())->method('getText')->willReturn($actual); + $select = $this->createMock(WebDriverSelect::class); + $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); + $command = $this->createPartialMock(AssertNotSelectedLabelCommand::class, ['getSelect']); + $command->expects($this->once())->method('getSelect')->with($element)->willReturn($select); + $command->run('partialLinkText=Language', 'United Kingdom', $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + ['United States', null], + ['United Kingdom', new Exception('Actual label "United Kingdom" did match "United Kingdom"')], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Assert/AssertNotSelectedValueCommandTest.php b/tests/Command/Assert/AssertNotSelectedValueCommandTest.php new file mode 100644 index 00000000..846ebf00 --- /dev/null +++ b/tests/Command/Assert/AssertNotSelectedValueCommandTest.php @@ -0,0 +1,83 @@ +expectExceptionObject($exception); + } + $element = $this->createMock(WebDriverElement::class); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'partial link text' === $selector->getMechanism() + && 'Language' === $selector->getValue(); + })) + ->willReturn($element); + $option = $this->createMock(WebDriverElement::class); + $option->expects($this->once())->method('getAttribute')->with('value')->willReturn($actual); + $select = $this->createMock(WebDriverSelect::class); + $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); + $command = $this->createPartialMock(AssertNotSelectedValueCommand::class, ['getSelect']); + $command->expects($this->once())->method('getSelect')->with($element)->willReturn($select); + $command->run('partialLinkText=Language', 'en_GB', $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + ['en_US', null], + ['en_GB', new Exception('Actual value "en_GB" did match "en_GB"')], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Assert/AssertNotTextCommandTest.php b/tests/Command/Assert/AssertNotTextCommandTest.php new file mode 100644 index 00000000..4f6df886 --- /dev/null +++ b/tests/Command/Assert/AssertNotTextCommandTest.php @@ -0,0 +1,80 @@ +expectExceptionObject($exception); + } + $element = $this->createMock(WebDriverElement::class); + $element->expects($this->once())->method('getText')->willReturn($actual); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'xpath' === $selector->getMechanism() + && '//h4[@href="#"]' === $selector->getValue(); + })) + ->willReturn($element); + $this->command->run('xpath=//h4[@href="#"]', 'Welcome to our store', $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + ['Goodbye! See you again', null], + [ + 'Welcome to our store', + new Exception('Actual text "Welcome to our store" did match "Welcome to our store"'), + ], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Assert/AssertSelectedLabelCommandTest.php b/tests/Command/Assert/AssertSelectedLabelCommandTest.php new file mode 100644 index 00000000..4bb9e888 --- /dev/null +++ b/tests/Command/Assert/AssertSelectedLabelCommandTest.php @@ -0,0 +1,83 @@ +expectExceptionObject($exception); + } + $element = $this->createMock(WebDriverElement::class); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'partial link text' === $selector->getMechanism() + && 'Language' === $selector->getValue(); + })) + ->willReturn($element); + $option = $this->createMock(WebDriverElement::class); + $option->expects($this->once())->method('getText')->willReturn($actual); + $select = $this->createMock(WebDriverSelect::class); + $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); + $command = $this->createPartialMock(AssertSelectedLabelCommand::class, ['getSelect']); + $command->expects($this->once())->method('getSelect')->with($element)->willReturn($select); + $command->run('partialLinkText=Language', 'United Kingdom', $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + ['United Kingdom', null], + ['United States', new Exception('Actual label "United States" did not match "United Kingdom"')], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Assert/AssertSelectedValueCommandTest.php b/tests/Command/Assert/AssertSelectedValueCommandTest.php new file mode 100644 index 00000000..835d19d6 --- /dev/null +++ b/tests/Command/Assert/AssertSelectedValueCommandTest.php @@ -0,0 +1,83 @@ +expectExceptionObject($exception); + } + $element = $this->createMock(WebDriverElement::class); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'partial link text' === $selector->getMechanism() + && 'Language' === $selector->getValue(); + })) + ->willReturn($element); + $option = $this->createMock(WebDriverElement::class); + $option->expects($this->once())->method('getAttribute')->with('value')->willReturn($actual); + $select = $this->createMock(WebDriverSelect::class); + $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); + $command = $this->createPartialMock(AssertSelectedValueCommand::class, ['getSelect']); + $command->expects($this->once())->method('getSelect')->with($element)->willReturn($select); + $command->run('partialLinkText=Language', 'en_GB', $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + ['en_GB', null], + ['en_US', new Exception('Actual value "en_US" did not match "en_GB"')], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Assert/AssertTextCommandTest.php b/tests/Command/Assert/AssertTextCommandTest.php new file mode 100644 index 00000000..c878adbd --- /dev/null +++ b/tests/Command/Assert/AssertTextCommandTest.php @@ -0,0 +1,80 @@ +expectExceptionObject($exception); + } + $element = $this->createMock(WebDriverElement::class); + $element->expects($this->once())->method('getText')->willReturn($actual); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'xpath' === $selector->getMechanism() + && '//h4[@href="#"]' === $selector->getValue(); + })) + ->willReturn($element); + $this->command->run('xpath=//h4[@href="#"]', 'Welcome to our store', $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + ['Welcome to our store', null], + [ + 'Goodbye! See you again', + new Exception('Actual text "Goodbye! See you again" did not match "Welcome to our store"'), + ], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Assert/AssertTitleCommandTest.php b/tests/Command/Assert/AssertTitleCommandTest.php new file mode 100644 index 00000000..9cba5485 --- /dev/null +++ b/tests/Command/Assert/AssertTitleCommandTest.php @@ -0,0 +1,64 @@ +expectExceptionObject($exception); + } + $this->driver->expects($this->exactly(2))->method('getTitle')->willReturn($actual); + $this->command->run('Welcome', null, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + ['Welcome', null], + ['Goodbye', new Exception('Actual title "Goodbye" did not match "Welcome"')], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', true], + ['anything', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Assert/AssertValueCommandTest.php b/tests/Command/Assert/AssertValueCommandTest.php new file mode 100644 index 00000000..0d8e1760 --- /dev/null +++ b/tests/Command/Assert/AssertValueCommandTest.php @@ -0,0 +1,81 @@ +expectExceptionObject($exception); + } + $element = $this->createMock(WebDriverElement::class); + $element + ->expects($this->once()) + ->method('getAttribute') + ->with('value') + ->willReturn($actual); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'css selector' === $selector->getMechanism() + && '.quality' === $selector->getValue(); + })) + ->willReturn($element); + $this->command->run('css=.quality', '14', $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + ['14', null], + ['15', new Exception('Actual value "15" did not match "14"')], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/CommandManagerTest.php b/tests/Command/CommandManagerTest.php new file mode 100644 index 00000000..8b286486 --- /dev/null +++ b/tests/Command/CommandManagerTest.php @@ -0,0 +1,211 @@ +httpClient = $this->createMock(HttpClientInterface::class); + $this->manager = new CommandManager($this->httpClient); + $this->driver = $this->createMock(RemoteWebDriver::class); + $this->values = $this->createMock(ValuesInterface::class); + } + + public function testHasCommand(): void + { + $this->assertFalse($this->manager->hasCommand('test')); + $this->assertTrue($this->manager->hasCommand('open')); + } + + public function testIsTargetMissing(): void + { + $this->assertFalse($this->manager->isTargetMissing('sendKeys', 'name=first-name')); + $this->assertTrue($this->manager->isTargetMissing('sendKeys', '')); + } + + public function testIsTargetNotValid(): void + { + $this->assertTrue($this->manager->isTargetNotValid('sendKeys', '.gender')); + $this->assertFalse($this->manager->isTargetNotValid('sendKeys', 'id=email')); + } + + public function testIsValueMissing(): void + { + $this->assertFalse($this->manager->isValueMissing('sendKeys', 'First Name')); + $this->assertTrue($this->manager->isValueMissing('sendKeys', '')); + } + + public function testIsValueNotValid(): void + { + $this->assertTrue($this->manager->isValueNotValid('clickAt', '123x456')); + $this->assertFalse($this->manager->isValueNotValid('clickAt', '67,89')); + } + + public function testRunCommand(): void + { + $command = 'command'; + $target = 'target'; + $value = 'value'; + $commandObject = $this->createMock(CommandInterface::class); + $commandObject + ->expects($this->once()) + ->method('run') + ->with('processed ' . $target, 'processed ' . $value, $this->values, $this->driver); + $this->manager = $this->createPartialMock(CommandManager::class, ['createCommand', 'process']); + $this->manager + ->expects($this->once()) + ->method('createCommand') + ->with($command) + ->willReturn($commandObject); + $this->manager + ->expects($this->exactly(2)) + ->method('process') + ->withConsecutive( + [$target, $this->values], + [$value, $this->values] + ) + ->willReturnCallback(fn (string $text) => 'processed ' . $text); + $this->manager->run($command, $target, $value, $this->values, $this->driver); + } + + /** + * @dataProvider textProvider + */ + public function testProcess(?string $text, array $values, ?string $expected): void + { + $method = (new \ReflectionMethod($this->manager, 'process')); + $this->assertSame($expected, $method->invoke($this->manager, $text, new Values($values))); + } + + public function textProvider(): array + { + return [ + [ + null, + [ + 'key1' => 'value1', + 'key2' => 'value2', + ], + null, + ], + [ + '', + [ + 'key1' => 'value1', + 'key2' => 'value2', + ], + '', + ], + [ + 'css=.id', + [ + 'key1' => 'value1', + 'key2' => 'value2', + ], + 'css=.id', + ], + [ + '${key}', + [ + 'key' => 'status', + ], + 'status', + ], + [ + 'variable1', + [ + 'variable1' => 123, + ], + 'variable1', + ], + [ + 'xpath=//div[text()=\'${variable1}\']/input', + [ + 'variable1' => 'value1', + 'variable2' => 'value2', + ], + 'xpath=//div[text()=\'value1\']/input', + ], + [ + 'http://example.com/${path}', + [], + 'http://example.com/path', + ], + ]; + } + + public function testCreateUploadCommand(): void + { + $this->manager->setUploadDir('/path/to/upload/files'); + $method = (new \ReflectionMethod($this->manager, 'createCommand')); + $command = $method->invoke($this->manager, 'upload'); + $property = (new \ReflectionProperty($command, 'uploadDir')); + $this->assertInstanceOf(UploadCommand::class, $command); + $this->assertSame('/path/to/upload/files', $property->getValue($command)); + } + + /** + * @dataProvider commandProvider + */ + public function testCreateHasHttpClientCommand(string $command, string $class): void + { + $method = (new \ReflectionMethod($this->manager, 'createCommand')); + $command = $method->invoke($this->manager, $command); + $property = (new \ReflectionProperty($command, 'httpClient')); + $this->assertInstanceOf($class, $command); + $this->assertSame($this->httpClient, $property->getValue($command)); + } + + public function commandProvider(): array + { + return [ + ['assertFileDownloaded', AssertFileDownloadedCommand::class], + ['assertClipboard', AssertClipboardCommand::class], + ['updateClipboard', UpdateClipboardCommand::class], + ]; + } + + public function testCreateValidCommand(): void + { + $method = (new \ReflectionMethod($this->manager, 'createCommand')); + $this->assertInstanceOf(ClickCommand::class, $method->invoke($this->manager, 'click')); + } + + public function testCreateInvalidCommand(): void + { + $method = (new \ReflectionMethod($this->manager, 'createCommand')); + $this->expectExceptionObject(new OutOfRangeException('Command newCommand not found')); + $method->invoke($this->manager, 'newCommand'); + } +} diff --git a/tests/Command/CommandPreprocessorTest.php b/tests/Command/CommandPreprocessorTest.php deleted file mode 100644 index 076ac292..00000000 --- a/tests/Command/CommandPreprocessorTest.php +++ /dev/null @@ -1,111 +0,0 @@ -setCommand($commandString); - $command->setTarget($target); - $command->setValue($value); - $preprocessor = new CommandPreprocessor(); - $newCommand = $preprocessor->process($command, new Values($values)); - $this->assertSame($commandString, $newCommand->getCommand()); - $this->assertSame($newTarget, $newCommand->getTarget()); - $this->assertSame($newValue, $newCommand->getValue()); - } - - public function commandProvider(): array - { - return [ - [ - KeyboardCommandRunner::TYPE, - 'xpath=//div[text()=\'${variable1}\']/input', - '${variable2}', - [ - 'variable1' => 'value1', - 'variable2' => 'value2', - ], - 'xpath=//div[text()=\'value1\']/input', - 'value2', - ], - [ - ScriptCommandRunner::EXECUTE_SCRIPT, - 'return ${variable1} + 1', - 'variable1', - [ - 'variable1' => 123, - ], - 'return 123 + 1', - 'variable1', - ], - [ - StoreCommandRunner::STORE, - 'closed', - '${key}', - [ - 'key' => 'status', - ], - 'closed', - 'status', - ], - [ - WindowCommandRunner::OPEN, - 'http://example.com/${path}', - null, - [], - 'http://example.com/path', - null, - ], - [ - MouseCommandRunner::CLICK, - 'css=.id', - null, - [ - 'key1' => 'value1', - 'key2' => 'value2', - ], - 'css=.id', - null, - ], - [ - 'invalid', - '', - null, - [ - 'key1' => 'value1', - 'key2' => 'value2', - ], - '', - null, - ], - ]; - } -} diff --git a/tests/Command/CommandRunnerManagerTest.php b/tests/Command/CommandRunnerManagerTest.php deleted file mode 100644 index 4f1fabfd..00000000 --- a/tests/Command/CommandRunnerManagerTest.php +++ /dev/null @@ -1,138 +0,0 @@ -runners = [ - $runner1 = $this->createMock(CommandRunnerInterface::class), - $runner2 = $this->createMock(CommandRunnerInterface::class), - $runner3 = $this->createMock(CommandRunnerInterface::class), - ]; - $this->preprocessor = $this->createMock(CommandPreprocessorInterface::class); - $this->manager = new CommandRunnerManager($this->runners, $this->preprocessor); - $this->command = $this->createMock(CommandInterface::class); - $this->processedCommand = $this->createMock(CommandInterface::class); - $this->driver = $this->createMock(RemoteWebDriver::class); - $this->values = $this->createMock(ValuesInterface::class); - } - - public function testGetAllCommands(): void - { - $this->runners[0]->expects($this->once())->method('getAllCommands')->willReturn([ - 'Action 1' => 'action1', - 'Action 2' => 'action2', - ]); - $this->runners[1]->expects($this->once())->method('getAllCommands')->willReturn([ - 'Action 3' => 'action3', - ]); - $this->runners[2]->expects($this->once())->method('getAllCommands')->willReturn([ - 'Action 4' => 'action4', - 'Action 5' => 'action5', - ]); - $this->assertSame([ - 'Action 1' => 'action1', - 'Action 2' => 'action2', - 'Action 3' => 'action3', - 'Action 4' => 'action4', - 'Action 5' => 'action5', - ], $this->manager->getAllCommands()); - } - - public function testGetCommandsRequireTarget(): void - { - $this->runners[0]->expects($this->once())->method('getCommandsRequireTarget')->willReturn([ - 'Action 1' => 'action1', - ]); - $this->runners[1]->expects($this->once())->method('getCommandsRequireTarget')->willReturn([ - 'Action 2' => 'action2', - 'Action 3' => 'action3', - ]); - $this->runners[2]->expects($this->once())->method('getCommandsRequireTarget')->willReturn([ - 'Action 4' => 'action4', - ]); - $this->assertSame([ - 'Action 1' => 'action1', - 'Action 2' => 'action2', - 'Action 3' => 'action3', - 'Action 4' => 'action4', - ], $this->manager->getCommandsRequireTarget()); - } - - public function testGetCommandsRequireValue(): void - { - $this->runners[0]->expects($this->once())->method('getCommandsRequireValue')->willReturn([ - 'Action 1' => 'action1', - 'Action 4' => 'action4', - ]); - $this->runners[1]->expects($this->once())->method('getCommandsRequireValue')->willReturn([ - 'Action 2' => 'action2', - 'Action 3' => 'action3', - ]); - $this->runners[2]->expects($this->once())->method('getCommandsRequireValue')->willReturn([ - 'Action 5' => 'action5', - ]); - $this->assertSame([ - 'Action 1' => 'action1', - 'Action 4' => 'action4', - 'Action 2' => 'action2', - 'Action 3' => 'action3', - 'Action 5' => 'action5', - ], $this->manager->getCommandsRequireValue()); - } - - /** - * @testWith [0] - * [1] - * [2] - */ - public function testRunCommand(int $support): void - { - foreach ($this->runners as $index => $runner) { - if ($index < $support) { - $runner->expects($this->once())->method('supports')->willReturn(false); - $runner->expects($this->never())->method('run'); - } elseif ($index === $support) { - $runner->expects($this->once())->method('supports')->willReturn(true); - $runner - ->expects($this->once()) - ->method('run') - ->with($this->processedCommand, $this->values, $this->driver); - } else { - $runner->expects($this->never())->method('supports'); - $runner->expects($this->never())->method('run'); - } - } - $this->preprocessor - ->expects($this->once()) - ->method('process') - ->with($this->command, $this->values) - ->willReturn($this->processedCommand); - $this->manager->run($this->command, $this->values, $this->driver); - } -} diff --git a/tests/Command/CommandTestCase.php b/tests/Command/CommandTestCase.php new file mode 100644 index 00000000..d4f05e56 --- /dev/null +++ b/tests/Command/CommandTestCase.php @@ -0,0 +1,78 @@ +driver = $this->createMock(RemoteWebDriver::class); + $this->command = $this->createCommand(); + $this->values = $this->createMock(ValuesInterface::class); + $this->element = $this->createMock(WebDriverElement::class); + } + + abstract protected function createCommand(): CommandInterface; + + /** + * @dataProvider targetProvider + */ + public function testValidateTarget(?string $target, bool $valid): void + { + $this->assertSame($valid, get_class($this->command)::validateTarget($target)); + } + + abstract public function targetProvider(): array; + + /** + * @dataProvider valueProvider + */ + public function testValidateValue(?string $value, bool $valid): void + { + $this->assertSame($valid, $this->command->validateValue($value)); + } + + abstract public function valueProvider(): array; + + public function testIsTargetRequired(): void + { + $this->assertSame($this->isTargetRequired, get_class($this->command)::isTargetRequired()); + } + + public function testIsValueRequired(): void + { + $this->assertSame($this->isValueRequired, get_class($this->command)::isValueRequired()); + } + + public function testGetTargetHelper(): void + { + $this->assertSame($this->targetHelper, get_class($this->command)::getTargetHelper()); + } + + public function testGetValueHelper(): void + { + $this->assertSame($this->valueHelper, get_class($this->command)::getValueHelper()); + } + + public function testGetGroup(): void + { + $this->assertSame($this->group, get_class($this->command)::getGroup()); + } +} diff --git a/tests/Command/Custom/AssertClipboardCommandTest.php b/tests/Command/Custom/AssertClipboardCommandTest.php new file mode 100644 index 00000000..92efc055 --- /dev/null +++ b/tests/Command/Custom/AssertClipboardCommandTest.php @@ -0,0 +1,98 @@ +httpClient); + } + + /** + * @dataProvider runProvider + */ + public function testRun(ResponseInterface|HttpClientException $response, ?Exception $exception): void + { + $this->setUpUrl(); + $this->driver->expects($this->once())->method('getSessionID')->willReturn($this->sessionId); + $mock = $this->httpClient + ->expects($this->once()) + ->method('request') + ->with( + 'GET', + $this->webdriverUri . '/clipboard/' . $this->sessionId + ); + if ($response instanceof ResponseInterface) { + $mock->willReturn($response); + } else { + $mock->willThrowException($response); + } + if ($exception) { + $this->expectExceptionObject($exception); + } + $this->command->run($this->clipboard, null, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + [$this->createResponse($this->clipboard), null], + [$this->createResponse('text 2'), new Exception( + "Failed expecting that clipboard's content equals '{$this->clipboard}', actual value 'text 2'" + )], + [ + new HttpClientException('Something wrong'), + new RuntimeException('Can not get clipboard: Something wrong'), + ], + ]; + } + + protected function createResponse(string $clipboard): ResponseInterface + { + $mock = $this->createMock(ResponseInterface::class); + $mock + ->expects($this->once()) + ->method('getContent') + ->willReturn($clipboard); + + return $mock; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', true], + ['anything', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Custom/AssertFileDownloadedCommandTest.php b/tests/Command/Custom/AssertFileDownloadedCommandTest.php new file mode 100644 index 00000000..3269832f --- /dev/null +++ b/tests/Command/Custom/AssertFileDownloadedCommandTest.php @@ -0,0 +1,95 @@ +httpClient); + } + + /** + * @dataProvider runProvider + */ + public function testRun(ResponseInterface|HttpClientException $response, ?Exception $exception): void + { + $this->setUpUrl(); + $this->driver->expects($this->once())->method('getSessionID')->willReturn($this->sessionId); + $mock = $this->httpClient + ->expects($this->once()) + ->method('request') + ->with( + 'GET', + $this->webdriverUri . '/download/' . $this->sessionId . '/file.txt' + ); + if ($response instanceof ResponseInterface) { + $mock->willReturn($response); + } else { + $mock->willThrowException($response); + } + if ($exception) { + $this->expectExceptionObject($exception); + } + $this->command->run('file.txt', null, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + [$this->createResponse(200), null], + [$this->createResponse(404), new Exception('Failed expecting that file file.txt is downloaded')], + [ + new HttpClientException('Something wrong'), + new RuntimeException('Can not get downloaded file file.txt: Something wrong'), + ], + ]; + } + + protected function createResponse(int $code): ResponseInterface + { + $mock = $this->createMock(ResponseInterface::class); + $mock + ->expects($this->once()) + ->method('getStatusCode') + ->willReturn($code); + + return $mock; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Custom/HasHttpClientCommandTestCase.php b/tests/Command/Custom/HasHttpClientCommandTestCase.php new file mode 100644 index 00000000..cb9f9c79 --- /dev/null +++ b/tests/Command/Custom/HasHttpClientCommandTestCase.php @@ -0,0 +1,35 @@ +httpClient = $this->createMock(HttpClientInterface::class); + $this->executor = $this->createMock(HttpCommandExecutor::class); + parent::setUp(); + } + + protected function setUpUrl(): void + { + $this->executor + ->expects($this->once()) + ->method('getAddressOfRemoteServer') + ->willReturn($this->webdriverUri . '/wd/hub'); + $this->driver + ->expects($this->once()) + ->method('getCommandExecutor') + ->willReturn($this->executor); + } +} diff --git a/tests/Command/Custom/UpdateClipboardCommandTest.php b/tests/Command/Custom/UpdateClipboardCommandTest.php new file mode 100644 index 00000000..f25c70b7 --- /dev/null +++ b/tests/Command/Custom/UpdateClipboardCommandTest.php @@ -0,0 +1,95 @@ +httpClient); + } + + /** + * @dataProvider runProvider + */ + public function testRun(ResponseInterface|HttpClientException $response, ?Exception $exception): void + { + $this->setUpUrl(); + $this->driver->expects($this->once())->method('getSessionID')->willReturn($this->sessionId); + $mock = $this->httpClient + ->expects($this->once()) + ->method('request') + ->with( + 'POST', + $this->webdriverUri . '/clipboard/' . $this->sessionId, + ['body' => 'clipboard'] + ); + if ($response instanceof ResponseInterface) { + $mock->willReturn($response); + } else { + $mock->willThrowException($response); + } + if ($exception) { + $this->expectExceptionObject($exception); + } + $this->command->run('clipboard', null, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + [$this->createResponse(123), null], + [ + new HttpClientException('Something wrong'), + new RuntimeException('Can not update clipboard: Something wrong'), + ], + ]; + } + + protected function createResponse(int $code): ResponseInterface + { + $mock = $this->createMock(ResponseInterface::class); + $mock + ->expects($this->once()) + ->method('getStatusCode') + ->willReturn($code); + + return $mock; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', true], + ['anything', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Custom/UploadCommandTest.php b/tests/Command/Custom/UploadCommandTest.php new file mode 100644 index 00000000..32287fc5 --- /dev/null +++ b/tests/Command/Custom/UploadCommandTest.php @@ -0,0 +1,73 @@ +uploadDir); + } + + public function testRun(): void + { + $this->element = $this->createMock(RemoteWebElement::class); + $this->element + ->expects($this->once()) + ->method('setFileDetector') + ->with($this->isInstanceOf(LocalFileDetector::class)) + ->willReturnSelf(); + $this->element + ->expects($this->once()) + ->method('sendKeys') + ->with($this->uploadDir . '/sub-directory/file.txt'); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'file_input' === $selector->getValue(); + })) + ->willReturn($this->element); + $this->command->run('id=file_input', 'sub-directory/file.txt', $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['video.mp4', true], + ]; + } +} diff --git a/tests/Command/Keyboard/SendKeysCommandTest.php b/tests/Command/Keyboard/SendKeysCommandTest.php new file mode 100644 index 00000000..2000a54e --- /dev/null +++ b/tests/Command/Keyboard/SendKeysCommandTest.php @@ -0,0 +1,59 @@ +createMock(WebDriverElement::class); + $element->expects($this->once())->method('click')->willReturnSelf(); + $element->expects($this->once())->method('sendKeys')->with('123'); + $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'css selector' === $selector->getMechanism() + && '.quantity' === $selector->getValue(); + }))->willReturn($element); + $this->command->run('css=.quantity', '123', $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Keyboard/TypeCommandTest.php b/tests/Command/Keyboard/TypeCommandTest.php new file mode 100644 index 00000000..d0623c33 --- /dev/null +++ b/tests/Command/Keyboard/TypeCommandTest.php @@ -0,0 +1,64 @@ +createMock(WebDriverElement::class); + $element->expects($this->once())->method('click')->willReturnSelf(); + $element->expects($this->once())->method('clear')->willReturnSelf(); + $element->expects($this->once())->method('sendKeys')->with('20 years old'); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'name' === $selector->getMechanism() + && 'age' === $selector->getValue(); + })) + ->willReturn($element); + $this->command->run('name=age', '20 years old', $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Mouse/AddSelectionCommandTest.php b/tests/Command/Mouse/AddSelectionCommandTest.php new file mode 100644 index 00000000..59a09b2c --- /dev/null +++ b/tests/Command/Mouse/AddSelectionCommandTest.php @@ -0,0 +1,84 @@ +createMock(WebDriverElement::class); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'partial link text' === $selector->getMechanism() + && 'Language' === $selector->getValue(); + })) + ->willReturn($element); + $select = $this->createMock(WebDriverSelect::class); + $select->expects($this->once())->method($method)->with($with); + $command = $this->createPartialMock(AddSelectionCommand::class, ['getSelect']); + $command->expects($this->once())->method('getSelect')->with($element)->willReturn($select); + $command->run('partialLinkText=Language', $value, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + ['index=123', 'selectByIndex', 123], + ['value=en_GB', 'selectByValue', 'en_GB'], + ['label=English (UK)', 'selectByVisibleText', 'English (UK)'], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['=', false], + ['key=value', false], + ['index=123', true], + ['value=abc123', true], + ['label=Text', true], + ]; + } +} diff --git a/tests/Command/Mouse/CheckCommandTest.php b/tests/Command/Mouse/CheckCommandTest.php new file mode 100644 index 00000000..f752d3ff --- /dev/null +++ b/tests/Command/Mouse/CheckCommandTest.php @@ -0,0 +1,74 @@ +createMock(WebDriverElement::class); + $element->expects($this->once())->method('isSelected')->willReturn($selected); + $element->expects($this->exactly(+$checked))->method('click'); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'language' === $selector->getValue(); + })) + ->willReturn($element); + $this->command->run('id=language', null, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + [true, false], + [false, true], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Mouse/ClickAtCommandTest.php b/tests/Command/Mouse/ClickAtCommandTest.php new file mode 100644 index 00000000..9f84acc5 --- /dev/null +++ b/tests/Command/Mouse/ClickAtCommandTest.php @@ -0,0 +1,68 @@ +createMock(WebDriverElement::class); + $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'cart' === $selector->getValue(); + }))->willReturn($element); + $action = $this->createMock(WebDriverActions::class); + $action->expects($this->once())->method('moveToElement')->with($element, 5, 10)->willReturnSelf(); + $action->expects($this->once())->method('click')->willReturnSelf(); + $action->expects($this->once())->method('perform'); + $this->driver->expects($this->once())->method('action')->willReturn($action); + $this->command->run('id=cart', '5,10', $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['123', false], + ['123,', false], + [',123', false], + ['123,456', true], + ]; + } +} diff --git a/tests/Command/Mouse/ClickCommandTest.php b/tests/Command/Mouse/ClickCommandTest.php new file mode 100644 index 00000000..6e04a4ed --- /dev/null +++ b/tests/Command/Mouse/ClickCommandTest.php @@ -0,0 +1,58 @@ +createMock(WebDriverElement::class); + $element->expects($this->once())->method('click'); + $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'add-to-cart' === $selector->getValue(); + }))->willReturn($element); + $this->command->run('id=add-to-cart', null, $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Mouse/DoubleClickAtCommandTest.php b/tests/Command/Mouse/DoubleClickAtCommandTest.php new file mode 100644 index 00000000..2fb71515 --- /dev/null +++ b/tests/Command/Mouse/DoubleClickAtCommandTest.php @@ -0,0 +1,68 @@ +createMock(WebDriverElement::class); + $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'cart' === $selector->getValue(); + }))->willReturn($element); + $action = $this->createMock(WebDriverActions::class); + $action->expects($this->once())->method('moveToElement')->with($element, 5, 10)->willReturnSelf(); + $action->expects($this->once())->method('doubleClick')->willReturnSelf(); + $action->expects($this->once())->method('perform'); + $this->driver->expects($this->once())->method('action')->willReturn($action); + $this->command->run('id=cart', '5,10', $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['123', false], + ['123,', false], + [',123', false], + ['123,456', true], + ]; + } +} diff --git a/tests/Command/Mouse/DoubleClickCommandTest.php b/tests/Command/Mouse/DoubleClickCommandTest.php new file mode 100644 index 00000000..2d4c3fd0 --- /dev/null +++ b/tests/Command/Mouse/DoubleClickCommandTest.php @@ -0,0 +1,62 @@ +createMock(WebDriverElement::class); + $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'cart' === $selector->getValue(); + }))->willReturn($element); + $action = $this->createMock(WebDriverActions::class); + $action->expects($this->once())->method('doubleClick')->with($element)->willReturnSelf(); + $action->expects($this->once())->method('perform'); + $this->driver->expects($this->once())->method('action')->willReturn($action); + $this->command->run('id=cart', null, $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Mouse/DragAndDropToObjectCommandTest.php b/tests/Command/Mouse/DragAndDropToObjectCommandTest.php new file mode 100644 index 00000000..3478dc56 --- /dev/null +++ b/tests/Command/Mouse/DragAndDropToObjectCommandTest.php @@ -0,0 +1,78 @@ +createMock(WebDriverElement::class); + $target = $this->createMock(WebDriverElement::class); + $this->driver + ->expects($this->exactly(2)) + ->method('findElement') + ->withConsecutive( + [$this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'product' === $selector->getValue(); + })], + [$this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'cart' === $selector->getValue(); + })], + )->willReturnOnConsecutiveCalls($source, $target); + $action = $this->createMock(WebDriverActions::class); + $action + ->expects($this->once()) + ->method('dragAndDrop') + ->with($source, $target) + ->willReturnSelf(); + $action->expects($this->once())->method('perform'); + $this->driver->expects($this->once())->method('action')->willReturn($action); + $this->command->run('id=product', 'id=cart', $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } +} diff --git a/tests/Command/Mouse/MouseDownAtCommandTest.php b/tests/Command/Mouse/MouseDownAtCommandTest.php new file mode 100644 index 00000000..2262b3a0 --- /dev/null +++ b/tests/Command/Mouse/MouseDownAtCommandTest.php @@ -0,0 +1,70 @@ +createMock(WebDriverCoordinates::class); + $element = $this->createMock(RemoteWebElement::class); + $element->expects($this->once())->method('getCoordinates')->willReturn($coord); + $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'cart' === $selector->getValue(); + }))->willReturn($element); + $mouse = $this->createMock(RemoteMouse::class); + $mouse->expects($this->once())->method('mouseMove')->with($coord, 5, 10)->willReturnSelf(); + $mouse->expects($this->once())->method('mouseDown'); + $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); + $this->command->run('id=cart', '5,10', $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['123', false], + ['123,', false], + [',123', false], + ['123,456', true], + ]; + } +} diff --git a/tests/Command/Mouse/MouseDownCommandTest.php b/tests/Command/Mouse/MouseDownCommandTest.php new file mode 100644 index 00000000..904e51c6 --- /dev/null +++ b/tests/Command/Mouse/MouseDownCommandTest.php @@ -0,0 +1,64 @@ +createMock(WebDriverCoordinates::class); + $element = $this->createMock(RemoteWebElement::class); + $element->expects($this->once())->method('getCoordinates')->willReturn($coord); + $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'cart' === $selector->getValue(); + }))->willReturn($element); + $mouse = $this->createMock(RemoteMouse::class); + $mouse->expects($this->once())->method('mouseDown')->with($coord); + $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); + $this->command->run('id=cart', null, $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Mouse/MouseMoveAtCommandTest.php b/tests/Command/Mouse/MouseMoveAtCommandTest.php new file mode 100644 index 00000000..b0f2202b --- /dev/null +++ b/tests/Command/Mouse/MouseMoveAtCommandTest.php @@ -0,0 +1,69 @@ +createMock(WebDriverCoordinates::class); + $element = $this->createMock(RemoteWebElement::class); + $element->expects($this->once())->method('getCoordinates')->willReturn($coord); + $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'cart' === $selector->getValue(); + }))->willReturn($element); + $mouse = $this->createMock(RemoteMouse::class); + $mouse->expects($this->once())->method('mouseMove')->with($coord, 5, 10); + $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); + $this->command->run('id=cart', '5,10', $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['123', false], + ['123,', false], + [',123', false], + ['123,456', true], + ]; + } +} diff --git a/tests/Command/Mouse/MouseOutCommandTest.php b/tests/Command/Mouse/MouseOutCommandTest.php new file mode 100644 index 00000000..f57af909 --- /dev/null +++ b/tests/Command/Mouse/MouseOutCommandTest.php @@ -0,0 +1,118 @@ +createMock(RemoteWebElement::class); + $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'cart' === $selector->getValue(); + }))->willReturn($element); + if ($exception) { + $this->driver->expects($this->never())->method('getMouse'); + $this->expectExceptionObject($exception); + } else { + $coord = $this->createMock(WebDriverCoordinates::class); + $element->expects($this->once())->method('getCoordinates')->willReturn($coord); + $mouse = $this->createMock(RemoteMouse::class); + $mouse->expects($this->once())->method('mouseMove')->with($coord, $x, $y); + $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); + } + $this->driver->expects($this->once())->method('executeScript')->with( + 'return [arguments[0].getBoundingClientRect(), {height: window.innerHeight, width: window.innerWidth}];', + [$element] + )->willReturn([$rect, $vp]); + $this->command->run('id=cart', null, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + [ + 'rect' => (object) ['top' => 1, 'height' => 2], + 'vp' => (object) [], + 'x' => null, + 'y' => -2, + 'exception' => null, + ], + [ + 'rect' => (object) ['top' => 0, 'right' => 2], + 'vp' => (object) ['width' => 3], + 'x' => 2, + 'y' => null, + 'exception' => null, + ], + [ + 'rect' => (object) ['top' => 0, 'right' => 2, 'bottom' => 1, 'height' => 4], + 'vp' => (object) ['width' => 2, 'height' => 2], + 'x' => null, + 'y' => 3, + 'exception' => null, + ], + [ + 'rect' => (object) ['top' => 0, 'right' => 2, 'bottom' => 1, 'left' => 1], + 'vp' => (object) ['width' => 2, 'height' => 1], + 'x' => -1, + 'y' => null, + 'exception' => null, + ], + [ + 'rect' => (object) ['top' => 0, 'right' => 2, 'bottom' => 1, 'left' => 0], + 'vp' => (object) ['width' => 2, 'height' => 1], + 'x' => null, + 'y' => null, + 'exception' => new Exception('Unable to perform mouse out as the element takes up the entire viewport'), + ], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Mouse/MouseOverCommandTest.php b/tests/Command/Mouse/MouseOverCommandTest.php new file mode 100644 index 00000000..6002b26f --- /dev/null +++ b/tests/Command/Mouse/MouseOverCommandTest.php @@ -0,0 +1,64 @@ +createMock(WebDriverCoordinates::class); + $element = $this->createMock(RemoteWebElement::class); + $element->expects($this->once())->method('getCoordinates')->willReturn($coord); + $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'cart' === $selector->getValue(); + }))->willReturn($element); + $mouse = $this->createMock(RemoteMouse::class); + $mouse->expects($this->once())->method('mouseMove')->with($coord); + $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); + $this->command->run('id=cart', null, $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Mouse/MouseUpAtCommandTest.php b/tests/Command/Mouse/MouseUpAtCommandTest.php new file mode 100644 index 00000000..b439f4bd --- /dev/null +++ b/tests/Command/Mouse/MouseUpAtCommandTest.php @@ -0,0 +1,70 @@ +createMock(WebDriverCoordinates::class); + $element = $this->createMock(RemoteWebElement::class); + $element->expects($this->once())->method('getCoordinates')->willReturn($coord); + $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'cart' === $selector->getValue(); + }))->willReturn($element); + $mouse = $this->createMock(RemoteMouse::class); + $mouse->expects($this->once())->method('mouseMove')->with($coord, 5, 10)->willReturnSelf(); + $mouse->expects($this->once())->method('mouseUp'); + $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); + $this->command->run('id=cart', '5,10', $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['123', false], + ['123,', false], + [',123', false], + ['123,456', true], + ]; + } +} diff --git a/tests/Command/Mouse/MouseUpCommandTest.php b/tests/Command/Mouse/MouseUpCommandTest.php new file mode 100644 index 00000000..7d067496 --- /dev/null +++ b/tests/Command/Mouse/MouseUpCommandTest.php @@ -0,0 +1,64 @@ +createMock(WebDriverCoordinates::class); + $element = $this->createMock(RemoteWebElement::class); + $element->expects($this->once())->method('getCoordinates')->willReturn($coord); + $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'cart' === $selector->getValue(); + }))->willReturn($element); + $mouse = $this->createMock(RemoteMouse::class); + $mouse->expects($this->once())->method('mouseUp')->with($coord); + $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); + $this->command->run('id=cart', null, $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Mouse/RemoveSelectionCommandTest.php b/tests/Command/Mouse/RemoveSelectionCommandTest.php new file mode 100644 index 00000000..ddd005d1 --- /dev/null +++ b/tests/Command/Mouse/RemoveSelectionCommandTest.php @@ -0,0 +1,84 @@ +createMock(WebDriverElement::class); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'partial link text' === $selector->getMechanism() + && 'Language' === $selector->getValue(); + })) + ->willReturn($element); + $select = $this->createMock(WebDriverSelect::class); + $select->expects($this->once())->method($method)->with($with); + $command = $this->createPartialMock(RemoveSelectionCommand::class, ['getSelect']); + $command->expects($this->once())->method('getSelect')->with($element)->willReturn($select); + $command->run('partialLinkText=Language', $value, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + ['index=123', 'deselectByIndex', 123], + ['value=en_GB', 'deselectByValue', 'en_GB'], + ['label=English (UK)', 'deselectByVisibleText', 'English (UK)'], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['=', false], + ['key=value', false], + ['index=123', true], + ['value=abc123', true], + ['label=Text', true], + ]; + } +} diff --git a/tests/Command/Mouse/SelectCommandTest.php b/tests/Command/Mouse/SelectCommandTest.php new file mode 100644 index 00000000..af05b1b3 --- /dev/null +++ b/tests/Command/Mouse/SelectCommandTest.php @@ -0,0 +1,65 @@ +createMock(WebDriverElement::class); + $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'partial link text' === $selector->getMechanism() + && 'Language' === $selector->getValue(); + }))->willReturn($select); + $option = $this->createMock(WebDriverElement::class); + $option->expects($this->once())->method('click'); + $select->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'css selector' === $selector->getMechanism() + && 'option[value=en_US]' === $selector->getValue(); + }))->willReturn($option); + $this->command->run('partialLinkText=Language', 'css=option[value=en_US]', $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } +} diff --git a/tests/Command/Mouse/UncheckCommandTest.php b/tests/Command/Mouse/UncheckCommandTest.php new file mode 100644 index 00000000..759293d0 --- /dev/null +++ b/tests/Command/Mouse/UncheckCommandTest.php @@ -0,0 +1,74 @@ +createMock(WebDriverElement::class); + $element->expects($this->once())->method('isSelected')->willReturn($selected); + $element->expects($this->exactly(+$unchecked))->method('click'); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'language' === $selector->getValue(); + })) + ->willReturn($element); + $this->command->run('id=language', null, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + [true, true], + [false, false], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Runner/AlertCommandRunnerTest.php b/tests/Command/Runner/AlertCommandRunnerTest.php deleted file mode 100644 index 42de9f40..00000000 --- a/tests/Command/Runner/AlertCommandRunnerTest.php +++ /dev/null @@ -1,102 +0,0 @@ -setCommand($acceptAlertCommand); - $alert = $this->createMock(WebDriverAlert::class); - $alert->expects($this->once())->method('accept'); - $targetLocator = $this->createMock(RemoteTargetLocator::class); - $targetLocator->expects($this->once())->method('alert')->willReturn($alert); - $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); - $this->runner->run($command, $this->values, $this->driver); - } - - /** - * @dataProvider dismissAlertCommandProvider - */ - public function testDismissAlert(string $acceptAlertCommand): void - { - $command = new Command(); - $command->setCommand($acceptAlertCommand); - $alert = $this->createMock(WebDriverAlert::class); - $alert->expects($this->once())->method('dismiss'); - $targetLocator = $this->createMock(RemoteTargetLocator::class); - $targetLocator->expects($this->once())->method('alert')->willReturn($alert); - $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAnswerPrompt(): void - { - $command = new Command(); - $command->setCommand(AlertCommandRunner::ANSWER_PROMPT); - $command->setTarget('Yes, I agree'); - $alert = $this->createMock(WebDriverAlert::class); - $alert->expects($this->once())->method('sendKeys')->with('Yes, I agree'); - $alert->expects($this->once())->method('accept'); - $targetLocator = $this->createMock(RemoteTargetLocator::class); - $targetLocator->expects($this->once())->method('alert')->willReturn($alert); - $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); - $this->runner->run($command, $this->values, $this->driver); - } - - public function acceptAlertCommandProvider(): array - { - return [ - [AlertCommandRunner::ACCEPT_ALERT], - [AlertCommandRunner::ACCEPT_CONFIRMATION], - ]; - } - - public function dismissAlertCommandProvider(): array - { - return [ - [AlertCommandRunner::DISMISS_CONFIRMATION], - [AlertCommandRunner::DISMISS_PROMPT], - ]; - } - - public function targetProvider(): array - { - return [ - [AlertCommandRunner::ANSWER_PROMPT, 'anything', true], - ]; - } - - public function commandsRequireTarget(): array - { - return [ - AlertCommandRunner::ANSWER_PROMPT, - ]; - } - - public function commandsRequireValue(): array - { - return []; - } -} diff --git a/tests/Command/Runner/AssertionRunnerTest.php b/tests/Command/Runner/AssertionRunnerTest.php deleted file mode 100644 index ea5097c3..00000000 --- a/tests/Command/Runner/AssertionRunnerTest.php +++ /dev/null @@ -1,643 +0,0 @@ -setCommand(AssertionRunner::ASSERT); - $command->setTarget('var name'); - $command->setValue('var value'); - $this->values->expects($this->once())->method('getValue')->with('var name')->willReturn('var value'); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertFailed(): void - { - $expected = 'var value'; - $actual = 'var value 1'; - $this->expectException(\Exception::class); - $this->expectExceptionMessage(sprintf('Actual value "%s" did not match "%s"', $actual, $expected)); - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT); - $command->setTarget('var name'); - $command->setValue($expected); - $this->values->expects($this->once())->method('getValue')->with('var name')->willReturn($actual); - $this->runner->run($command, $this->values, $this->driver); - } - - /** - * @dataProvider alertCommandProvider - */ - public function testAssertAlertPassed(string $alertCommand): void - { - $command = new Command(); - $command->setCommand($alertCommand); - $command->setTarget('Are you sure you want to close this window?'); - $alert = $this->createMock(WebDriverAlert::class); - $alert->expects($this->once())->method('getText')->willReturn('Are you sure you want to close this window?'); - $locator = $this->createMock(RemoteTargetLocator::class); - $locator->expects($this->once())->method('alert')->willReturn($alert); - $this->driver->expects($this->once())->method('switchTo')->willReturn($locator); - $this->runner->run($command, $this->values, $this->driver); - } - - /** - * @dataProvider alertCommandProvider - */ - public function testAssertAlertFailed(string $alertCommand, string $type): void - { - $expected = 'Are you sure you want to close this window?'; - $actual = 'Dont close this window!'; - $this->expectException(\Exception::class); - $this->expectExceptionMessage(sprintf('Actual %s text "%s" did not match "%s"', $type, $actual, $expected)); - $command = new Command(); - $command->setCommand($alertCommand); - $command->setTarget($expected); - $alert = $this->createMock(WebDriverAlert::class); - $alert->expects($this->once())->method('getText')->willReturn($actual); - $locator = $this->createMock(RemoteTargetLocator::class); - $locator->expects($this->once())->method('alert')->willReturn($alert); - $this->driver->expects($this->once())->method('switchTo')->willReturn($locator); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertTitlePassed(): void - { - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_TITLE); - $command->setTarget('Welcome'); - $this->driver->expects($this->exactly(2))->method('getTitle')->willReturn('Welcome'); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertTitleFailed(): void - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Actual title "Goodbye" did not match "Welcome"'); - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_TITLE); - $command->setTarget('Welcome'); - $this->driver->expects($this->exactly(2))->method('getTitle')->willReturn('Goodbye'); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertTextPassed(): void - { - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_TEXT); - $command->setTarget('xpath=//h4[@href="#"]'); - $command->setValue('Welcome to our store'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('getText')->willReturn('Welcome to our store'); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'xpath' === $selector->getMechanism() - && '//h4[@href="#"]' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertTextFailed(): void - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Actual text "Goodbye! See you again" did not match "Welcome to our store"'); - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_TEXT); - $command->setTarget('xpath=//h4[@href="#"]'); - $command->setValue('Welcome to our store'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('getText')->willReturn('Goodbye! See you again'); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'xpath' === $selector->getMechanism() - && '//h4[@href="#"]' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertNotTextFailed(): void - { - $expected = 'Welcome to our store'; - $this->expectException(\Exception::class); - $this->expectExceptionMessage(sprintf('Actual text "%s" did match "%s"', $expected, $expected)); - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_NOT_TEXT); - $command->setTarget('xpath=//h4[@href="#"]'); - $command->setValue($expected); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('getText')->willReturn($expected); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'xpath' === $selector->getMechanism() - && '//h4[@href="#"]' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertNotTextPassed(): void - { - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_NOT_TEXT); - $command->setTarget('xpath=//h4[@href="#"]'); - $command->setValue('Welcome to our store'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('getText')->willReturn('Goodbye! See you again'); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'xpath' === $selector->getMechanism() - && '//h4[@href="#"]' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertValuePassed(): void - { - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_VALUE); - $command->setTarget('css=.quality'); - $command->setValue('14'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('getAttribute')->with('value')->willReturn('14'); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && '.quality' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertValueFailed(): void - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Actual value "15" did not match "14"'); - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_VALUE); - $command->setTarget('css=.quality'); - $command->setValue('14'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('getAttribute')->with('value')->willReturn('15'); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && '.quality' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertEditablePassed(): void - { - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_EDITABLE); - $command->setTarget('name=username'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'name' === $selector->getMechanism() - && 'username' === $selector->getValue(); - }))->willReturn($element); - $this->driver - ->expects($this->once()) - ->method('executeScript') - ->with('return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', [$element]) - ->willReturn((object) ['enabled' => true, 'readonly' => false]); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertEditableFailed(): void - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Element "name=username" is not editable'); - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_EDITABLE); - $command->setTarget('name=username'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'name' === $selector->getMechanism() - && 'username' === $selector->getValue(); - }))->willReturn($element); - $this->driver - ->expects($this->once()) - ->method('executeScript') - ->with('return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', [$element]) - ->willReturn((object) ['enabled' => false, 'readonly' => true]); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertNotEditableFailed(): void - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Element "name=username" is editable'); - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_NOT_EDITABLE); - $command->setTarget('name=username'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'name' === $selector->getMechanism() - && 'username' === $selector->getValue(); - }))->willReturn($element); - $this->driver - ->expects($this->once()) - ->method('executeScript') - ->with('return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', [$element]) - ->willReturn((object) ['enabled' => true, 'readonly' => false]); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertNotEditablePassed(): void - { - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_NOT_EDITABLE); - $command->setTarget('name=username'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'name' === $selector->getMechanism() - && 'username' === $selector->getValue(); - }))->willReturn($element); - $this->driver - ->expects($this->once()) - ->method('executeScript') - ->with('return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', [$element]) - ->willReturn((object) ['enabled' => false, 'readonly' => true]); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertElementPresentPassed(): void - { - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_ELEMENT_PRESENT); - $command->setTarget('css=.cart'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElements')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && '.cart' === $selector->getValue(); - }))->willReturn([$element]); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertElementPresentFailed(): void - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Expected element "css=.cart" was not found in page'); - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_ELEMENT_PRESENT); - $command->setTarget('css=.cart'); - $this->driver->expects($this->once())->method('findElements')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && '.cart' === $selector->getValue(); - }))->willReturn([]); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertElementNotPresentFailed(): void - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Unexpected element "css=.cart" was found in page'); - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_ELEMENT_NOT_PRESENT); - $command->setTarget('css=.cart'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElements')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && '.cart' === $selector->getValue(); - }))->willReturn([$element]); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertElementNotPresentPassed(): void - { - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_ELEMENT_NOT_PRESENT); - $command->setTarget('css=.cart'); - $this->driver->expects($this->once())->method('findElements')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && '.cart' === $selector->getValue(); - }))->willReturn([]); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertCheckedPassed(): void - { - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_CHECKED); - $command->setTarget('css=.term-and-condition'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('isSelected')->willReturn(true); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && '.term-and-condition' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertCheckedFailed(): void - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Element "css=.term-and-condition" is not checked, expected to be checked'); - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_CHECKED); - $command->setTarget('css=.term-and-condition'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('isSelected')->willReturn(false); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && '.term-and-condition' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertNotCheckedFailed(): void - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Element "css=.term-and-condition" is checked, expected to be unchecked'); - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_NOT_CHECKED); - $command->setTarget('css=.term-and-condition'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('isSelected')->willReturn(true); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && '.term-and-condition' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertNotCheckedPassed(): void - { - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_NOT_CHECKED); - $command->setTarget('css=.term-and-condition'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('isSelected')->willReturn(false); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && '.term-and-condition' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testAssertSelectedValuePassed(): void - { - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_SELECTED_VALUE); - $command->setTarget('partialLinkText=Language'); - $command->setValue('en_GB'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'partial link text' === $selector->getMechanism() - && 'Language' === $selector->getValue(); - }))->willReturn($element); - $option = $this->createMock(WebDriverElement::class); - $option->expects($this->once())->method('getAttribute')->with('value')->willReturn('en_GB'); - $select = $this->createMock(WebDriverSelect::class); - $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); - $runner = $this->createPartialMock(AssertionRunner::class, ['getSelect']); - $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->values, $this->driver); - } - - public function testAssertSelectedValueFailed(): void - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Actual value "en_US" did not match "en_GB"'); - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_SELECTED_VALUE); - $command->setTarget('partialLinkText=Language'); - $command->setValue('en_GB'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'partial link text' === $selector->getMechanism() - && 'Language' === $selector->getValue(); - }))->willReturn($element); - $option = $this->createMock(WebDriverElement::class); - $option->expects($this->once())->method('getAttribute')->with('value')->willReturn('en_US'); - $select = $this->createMock(WebDriverSelect::class); - $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); - $runner = $this->createPartialMock(AssertionRunner::class, ['getSelect']); - $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->values, $this->driver); - } - - public function testAssertNotSelectedValueFailed(): void - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Actual value "en_GB" did match "en_GB"'); - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_NOT_SELECTED_VALUE); - $command->setTarget('partialLinkText=Language'); - $command->setValue('en_GB'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'partial link text' === $selector->getMechanism() - && 'Language' === $selector->getValue(); - }))->willReturn($element); - $option = $this->createMock(WebDriverElement::class); - $option->expects($this->once())->method('getAttribute')->with('value')->willReturn('en_GB'); - $select = $this->createMock(WebDriverSelect::class); - $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); - $runner = $this->createPartialMock(AssertionRunner::class, ['getSelect']); - $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->values, $this->driver); - } - - public function testAssertNotSelectedValuePassed(): void - { - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_NOT_SELECTED_VALUE); - $command->setTarget('partialLinkText=Language'); - $command->setValue('en_GB'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'partial link text' === $selector->getMechanism() - && 'Language' === $selector->getValue(); - }))->willReturn($element); - $option = $this->createMock(WebDriverElement::class); - $option->expects($this->once())->method('getAttribute')->with('value')->willReturn('en_US'); - $select = $this->createMock(WebDriverSelect::class); - $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); - $runner = $this->createPartialMock(AssertionRunner::class, ['getSelect']); - $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->values, $this->driver); - } - - public function testAssertSelectedLabelPassed(): void - { - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_SELECTED_LABEL); - $command->setTarget('partialLinkText=Language'); - $command->setValue('United Kingdom'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'partial link text' === $selector->getMechanism() - && 'Language' === $selector->getValue(); - }))->willReturn($element); - $option = $this->createMock(WebDriverElement::class); - $option->expects($this->once())->method('getText')->willReturn('United Kingdom'); - $select = $this->createMock(WebDriverSelect::class); - $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); - $runner = $this->createPartialMock(AssertionRunner::class, ['getSelect']); - $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->values, $this->driver); - } - - public function testAssertSelectedLabelFailed(): void - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Actual label "United States" did not match "United Kingdom"'); - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_SELECTED_LABEL); - $command->setTarget('partialLinkText=Language'); - $command->setValue('United Kingdom'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'partial link text' === $selector->getMechanism() - && 'Language' === $selector->getValue(); - }))->willReturn($element); - $option = $this->createMock(WebDriverElement::class); - $option->expects($this->once())->method('getText')->willReturn('United States'); - $select = $this->createMock(WebDriverSelect::class); - $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); - $runner = $this->createPartialMock(AssertionRunner::class, ['getSelect']); - $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->values, $this->driver); - } - - public function testAssertNotSelectedLabelFailed(): void - { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Actual label "United Kingdom" did match "United Kingdom"'); - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_NOT_SELECTED_LABEL); - $command->setTarget('partialLinkText=Language'); - $command->setValue('United Kingdom'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'partial link text' === $selector->getMechanism() - && 'Language' === $selector->getValue(); - }))->willReturn($element); - $option = $this->createMock(WebDriverElement::class); - $option->expects($this->once())->method('getText')->willReturn('United Kingdom'); - $select = $this->createMock(WebDriverSelect::class); - $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); - $runner = $this->createPartialMock(AssertionRunner::class, ['getSelect']); - $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->values, $this->driver); - } - - public function testAssertNotSelectedLabelPassed(): void - { - $command = new Command(); - $command->setCommand(AssertionRunner::ASSERT_NOT_SELECTED_LABEL); - $command->setTarget('partialLinkText=Language'); - $command->setValue('United Kingdom'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'partial link text' === $selector->getMechanism() - && 'Language' === $selector->getValue(); - }))->willReturn($element); - $option = $this->createMock(WebDriverElement::class); - $option->expects($this->once())->method('getText')->willReturn('United States'); - $select = $this->createMock(WebDriverSelect::class); - $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); - $runner = $this->createPartialMock(AssertionRunner::class, ['getSelect']); - $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->values, $this->driver); - } - - public function alertCommandProvider(): array - { - return [ - [AssertionRunner::ASSERT_ALERT, 'alert'], - [AssertionRunner::ASSERT_CONFIRMATION, 'confirm'], - [AssertionRunner::ASSERT_PROMPT, 'prompt'], - ]; - } - - public function targetProvider(): array - { - return [ - [AssertionRunner::ASSERT_TEXT, 'invalid=anything', false], - [AssertionRunner::ASSERT_VALUE, 'css=.age', true], - [AssertionRunner::ASSERT, 'anything', true], - ]; - } - - public function commandsRequireTarget(): array - { - return [ - AssertionRunner::ASSERT, - AssertionRunner::ASSERT_ALERT, - AssertionRunner::ASSERT_CONFIRMATION, - AssertionRunner::ASSERT_PROMPT, - AssertionRunner::ASSERT_TITLE, - AssertionRunner::ASSERT_TEXT, - AssertionRunner::ASSERT_NOT_TEXT, - AssertionRunner::ASSERT_VALUE, - AssertionRunner::ASSERT_EDITABLE, - AssertionRunner::ASSERT_NOT_EDITABLE, - AssertionRunner::ASSERT_ELEMENT_PRESENT, - AssertionRunner::ASSERT_ELEMENT_NOT_PRESENT, - AssertionRunner::ASSERT_CHECKED, - AssertionRunner::ASSERT_NOT_CHECKED, - AssertionRunner::ASSERT_SELECTED_VALUE, - AssertionRunner::ASSERT_NOT_SELECTED_VALUE, - AssertionRunner::ASSERT_SELECTED_LABEL, - AssertionRunner::ASSERT_NOT_SELECTED_LABEL, - ]; - } - - public function commandsRequireValue(): array - { - return [ - AssertionRunner::ASSERT, - AssertionRunner::ASSERT_TEXT, - AssertionRunner::ASSERT_NOT_TEXT, - AssertionRunner::ASSERT_VALUE, - AssertionRunner::ASSERT_SELECTED_VALUE, - AssertionRunner::ASSERT_NOT_SELECTED_VALUE, - AssertionRunner::ASSERT_SELECTED_LABEL, - AssertionRunner::ASSERT_NOT_SELECTED_LABEL, - ]; - } -} diff --git a/tests/Command/Runner/CustomCommandRunnerTest.php b/tests/Command/Runner/CustomCommandRunnerTest.php deleted file mode 100644 index 0bb2b7d0..00000000 --- a/tests/Command/Runner/CustomCommandRunnerTest.php +++ /dev/null @@ -1,243 +0,0 @@ -httpClient = $this->createMock(HttpClientInterface::class); - $runner = new CustomCommandRunner($this->httpClient); - $runner->setWebdriverUri($this->webdriverUri); - $runner->setUploadDir($this->uploadDir); - - return $runner; - } - - public function testUpload(): void - { - $command = new Command(); - $command->setCommand(CustomCommandRunner::UPLOAD); - $command->setTarget('id=file_input'); - $command->setValue('sub-directory/file.txt'); - $this->element = $this->createMock(RemoteWebElement::class); - $this->element - ->expects($this->once()) - ->method('setFileDetector') - ->with($this->isInstanceOf(LocalFileDetector::class)) - ->willReturnSelf(); - $this->element - ->expects($this->once()) - ->method('sendKeys') - ->with($this->uploadDir . '/sub-directory/file.txt'); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'file_input' === $selector->getValue(); - }))->willReturn($this->element); - $this->runner->run($command, $this->values, $this->driver); - } - - /** - * @dataProvider statusCodeProvider - */ - public function testAssertFileDownloaded(int $code): void - { - $command = new Command(); - $command->setCommand(CustomCommandRunner::ASSERT_FILE_DOWNLOADED); - $command->setTarget('file.txt'); - $response = $this->createMock(ResponseInterface::class); - $response->expects($this->once())->method('getStatusCode')->willReturn($code); - $this->driver->expects($this->once())->method('getSessionID')->willReturn($this->sessionId); - $this->httpClient - ->expects($this->once()) - ->method('request') - ->with( - 'GET', - $this->webdriverUri . '/download/' . $this->sessionId . '/file.txt' - ) - ->willReturn($response); - if (200 !== $code) { - $this->expectException(Exception::class); - $this->expectExceptionMessage('Failed expecting that file file.txt is downloaded'); - } - $this->runner->run($command, $this->values, $this->driver); - } - - public function statusCodeProvider(): array - { - return [ - [200], - [404], - ]; - } - - public function testAssertFileDownloadedThrowException(): void - { - $command = new Command(); - $command->setCommand(CustomCommandRunner::ASSERT_FILE_DOWNLOADED); - $command->setTarget('file.txt'); - $this->runner->setWebdriverUri($this->webdriverUri); - $this->driver->expects($this->once())->method('getSessionID')->willReturn($this->sessionId); - $this->httpClient - ->expects($this->once()) - ->method('request') - ->with( - 'GET', - $this->webdriverUri . '/download/' . $this->sessionId . '/file.txt' - ) - ->willThrowException(new HttpClientException('Something wrong')); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Can not get downloaded file file.txt: Something wrong'); - $this->runner->run($command, $this->values, $this->driver); - } - - /** - * @dataProvider clipboardProvider - */ - public function testAssertClipbard(string $clipboard): void - { - $command = new Command(); - $command->setCommand(CustomCommandRunner::ASSERT_CLIPBOARD); - $command->setTarget($this->clipboard); - $response = $this->createMock(ResponseInterface::class); - $response->expects($this->once())->method('getContent')->willReturn($clipboard); - $this->driver->expects($this->once())->method('getSessionID')->willReturn($this->sessionId); - $this->httpClient - ->expects($this->once()) - ->method('request') - ->with( - 'GET', - $this->webdriverUri . '/clipboard/' . $this->sessionId - ) - ->willReturn($response); - if ($clipboard !== $this->clipboard) { - $this->expectException(Exception::class); - $this->expectExceptionMessage(sprintf( - "Failed expecting that clipboard's content equals '%s', actual value '%s'", - $this->clipboard, - $clipboard - )); - } - $this->runner->run($command, $this->values, $this->driver); - } - - public function clipboardProvider(): array - { - return [ - [$this->clipboard], - ['text 2'], - ]; - } - - public function testAssertClipboardThrowException(): void - { - $command = new Command(); - $command->setCommand(CustomCommandRunner::ASSERT_CLIPBOARD); - $command->setTarget($this->clipboard); - $this->runner->setWebdriverUri($this->webdriverUri); - $this->driver->expects($this->once())->method('getSessionID')->willReturn($this->sessionId); - $this->httpClient - ->expects($this->once()) - ->method('request') - ->with( - 'GET', - $this->webdriverUri . '/clipboard/' . $this->sessionId - ) - ->willThrowException(new HttpClientException('Something wrong')); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Can not get clipboard: Something wrong'); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testUpdateClipboard(): void - { - $command = new Command(); - $command->setCommand(CustomCommandRunner::UPDATE_CLIPBOARD); - $command->setTarget('clipboard'); - $response = $this->createMock(ResponseInterface::class); - $response->expects($this->once())->method('getStatusCode')->willReturn(123); - $this->driver->expects($this->once())->method('getSessionID')->willReturn($this->sessionId); - $this->httpClient - ->expects($this->once()) - ->method('request') - ->with( - 'POST', - $this->webdriverUri . '/clipboard/' . $this->sessionId, - ['body' => 'clipboard'] - ) - ->willReturn($response); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testUpdateClipboardThrowException(): void - { - $command = new Command(); - $command->setCommand(CustomCommandRunner::UPDATE_CLIPBOARD); - $command->setTarget('text'); - $this->runner->setWebdriverUri($this->webdriverUri); - $this->driver->expects($this->once())->method('getSessionID')->willReturn($this->sessionId); - $this->httpClient - ->expects($this->once()) - ->method('request') - ->with( - 'POST', - $this->webdriverUri . '/clipboard/' . $this->sessionId, - ['body' => 'text'] - ) - ->willThrowException(new HttpClientException('Something wrong')); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Can not update clipboard: Something wrong'); - $this->runner->run($command, $this->values, $this->driver); - } - - public function targetProvider(): array - { - return [ - [CustomCommandRunner::UPLOAD, null, false], - [CustomCommandRunner::UPLOAD, 'anything', false], - [CustomCommandRunner::UPLOAD, 'xpath=//path/to/element', true], - ]; - } - - public function commandsRequireTarget(): array - { - return [ - CustomCommandRunner::UPLOAD, - CustomCommandRunner::ASSERT_FILE_DOWNLOADED, - CustomCommandRunner::ASSERT_CLIPBOARD, - CustomCommandRunner::UPDATE_CLIPBOARD, - ]; - } - - public function commandsRequireValue(): array - { - return [ - CustomCommandRunner::UPLOAD, - ]; - } -} diff --git a/tests/Command/Runner/KeyboardCommandRunnerTest.php b/tests/Command/Runner/KeyboardCommandRunnerTest.php deleted file mode 100644 index 276d2719..00000000 --- a/tests/Command/Runner/KeyboardCommandRunnerTest.php +++ /dev/null @@ -1,80 +0,0 @@ -setCommand(KeyboardCommandRunner::TYPE); - $command->setTarget('name=age'); - $command->setValue('20 years old'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('click')->willReturnSelf(); - $element->expects($this->once())->method('clear')->willReturnSelf(); - $element->expects($this->once())->method('sendKeys')->with(['20 years old']); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'name' === $selector->getMechanism() - && 'age' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testSendKeys(): void - { - $command = new Command(); - $command->setCommand(KeyboardCommandRunner::SEND_KEYS); - $command->setTarget('css=.quantity'); - $command->setValue('123'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('click')->willReturnSelf(); - $element->expects($this->once())->method('sendKeys')->with(['123']); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && '.quantity' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function targetProvider(): array - { - return [ - [KeyboardCommandRunner::SEND_KEYS, null, false], - [KeyboardCommandRunner::SEND_KEYS, 'anything', false], - [KeyboardCommandRunner::SEND_KEYS, 'xpath=//path/to/element', true], - ]; - } - - public function commandsRequireTarget(): array - { - return [ - KeyboardCommandRunner::TYPE, - KeyboardCommandRunner::SEND_KEYS, - ]; - } - - public function commandsRequireValue(): array - { - return []; - } -} diff --git a/tests/Command/Runner/MouseCommandRunnerTest.php b/tests/Command/Runner/MouseCommandRunnerTest.php deleted file mode 100644 index 2b42ec12..00000000 --- a/tests/Command/Runner/MouseCommandRunnerTest.php +++ /dev/null @@ -1,616 +0,0 @@ -setCommand(MouseCommandRunner::ADD_SELECTION); - $command->setTarget('partialLinkText=Language'); - $command->setValue('index=123'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'partial link text' === $selector->getMechanism() - && 'Language' === $selector->getValue(); - }))->willReturn($element); - $select = $this->createMock(WebDriverSelect::class); - $select->expects($this->once())->method('selectByIndex')->with(123); - $runner = $this->createPartialMock(MouseCommandRunner::class, ['getSelect']); - $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->values, $this->driver); - } - - public function testAddSelectionByValue(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::ADD_SELECTION); - $command->setTarget('partialLinkText=Language'); - $command->setValue('value=en_GB'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'partial link text' === $selector->getMechanism() - && 'Language' === $selector->getValue(); - }))->willReturn($element); - $select = $this->createMock(WebDriverSelect::class); - $select->expects($this->once())->method('selectByValue')->with('en_GB'); - $runner = $this->createPartialMock(MouseCommandRunner::class, ['getSelect']); - $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->values, $this->driver); - } - - public function testAddSelectionByLabel(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::ADD_SELECTION); - $command->setTarget('partialLinkText=Language'); - $command->setValue('label=English (UK)'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'partial link text' === $selector->getMechanism() - && 'Language' === $selector->getValue(); - }))->willReturn($element); - $select = $this->createMock(WebDriverSelect::class); - $select->expects($this->once())->method('selectByVisibleText')->with('English (UK)'); - $runner = $this->createPartialMock(MouseCommandRunner::class, ['getSelect']); - $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->values, $this->driver); - } - - public function testRemoveSelectionByIndex(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::REMOVE_SELECTION); - $command->setTarget('partialLinkText=Language'); - $command->setValue('index=123'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'partial link text' === $selector->getMechanism() - && 'Language' === $selector->getValue(); - }))->willReturn($element); - $select = $this->createMock(WebDriverSelect::class); - $select->expects($this->once())->method('deselectByIndex')->with(123); - $runner = $this->createPartialMock(MouseCommandRunner::class, ['getSelect']); - $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->values, $this->driver); - } - - public function testRemoveSelectionByValue(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::REMOVE_SELECTION); - $command->setTarget('partialLinkText=Language'); - $command->setValue('value=en_GB'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'partial link text' === $selector->getMechanism() - && 'Language' === $selector->getValue(); - }))->willReturn($element); - $select = $this->createMock(WebDriverSelect::class); - $select->expects($this->once())->method('deselectByValue')->with('en_GB'); - $runner = $this->createPartialMock(MouseCommandRunner::class, ['getSelect']); - $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->values, $this->driver); - } - - public function testRemoveSelectionByLabel(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::REMOVE_SELECTION); - $command->setTarget('partialLinkText=Language'); - $command->setValue('label=English (UK)'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'partial link text' === $selector->getMechanism() - && 'Language' === $selector->getValue(); - }))->willReturn($element); - $select = $this->createMock(WebDriverSelect::class); - $select->expects($this->once())->method('deselectByVisibleText')->with('English (UK)'); - $runner = $this->createPartialMock(MouseCommandRunner::class, ['getSelect']); - $runner->expects($this->once())->method('getSelect')->with($element)->willReturn($select); - $runner->run($command, $this->values, $this->driver); - } - - /** - * @dataProvider checkDataProvider - */ - public function testCheck(bool $selected, bool $checked): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::CHECK); - $command->setTarget('id=language'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('isSelected')->willReturn($selected); - $element->expects($this->exactly(+$checked))->method('click'); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'language' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - /** - * @dataProvider uncheckDataProvider - */ - public function testUncheck(bool $selected, bool $unchecked): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::UNCHECK); - $command->setTarget('id=language'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('isSelected')->willReturn($selected); - $element->expects($this->exactly(+$unchecked))->method('click'); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'language' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testClick(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::CLICK); - $command->setTarget('id=add-to-cart'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('click'); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'add-to-cart' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testClickAt(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::CLICK_AT); - $command->setTarget('id=cart'); - $command->setValue('5,10'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'cart' === $selector->getValue(); - }))->willReturn($element); - $action = $this->createMock(WebDriverActions::class); - $action->expects($this->once())->method('moveToElement')->with($element, 5, 10)->willReturnSelf(); - $action->expects($this->once())->method('click')->willReturnSelf(); - $action->expects($this->once())->method('perform'); - $this->driver->expects($this->once())->method('action')->willReturn($action); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testDoubleClick(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::DOUBLE_CLICK); - $command->setTarget('id=cart'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'cart' === $selector->getValue(); - }))->willReturn($element); - $action = $this->createMock(WebDriverActions::class); - $action->expects($this->once())->method('doubleClick')->with($element)->willReturnSelf(); - $action->expects($this->once())->method('perform'); - $this->driver->expects($this->once())->method('action')->willReturn($action); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testDoubleClickAt(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::DOUBLE_CLICK_AT); - $command->setTarget('id=cart'); - $command->setValue('5,10'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'cart' === $selector->getValue(); - }))->willReturn($element); - $action = $this->createMock(WebDriverActions::class); - $action->expects($this->once())->method('moveToElement')->with($element, 5, 10)->willReturnSelf(); - $action->expects($this->once())->method('doubleClick')->willReturnSelf(); - $action->expects($this->once())->method('perform'); - $this->driver->expects($this->once())->method('action')->willReturn($action); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testDragAndDropToObject(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::DRAG_AND_DROP_TO_OBJECT); - $command->setTarget('id=product'); - $command->setValue('id=cart'); - $source = $this->createMock(WebDriverElement::class); - $target = $this->createMock(WebDriverElement::class); - $this->driver - ->expects($this->exactly(2)) - ->method('findElement') - ->withConsecutive( - [$this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'product' === $selector->getValue(); - })], - [$this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'cart' === $selector->getValue(); - })], - )->willReturnOnConsecutiveCalls($source, $target); - $action = $this->createMock(WebDriverActions::class); - $action->expects($this->once())->method('dragAndDrop')->with($source, $target)->willReturnSelf(); - $action->expects($this->once())->method('perform'); - $this->driver->expects($this->once())->method('action')->willReturn($action); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testMouseDown(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::MOUSE_DOWN); - $command->setTarget('id=cart'); - $coord = $this->createMock(WebDriverCoordinates::class); - $element = $this->createMock(RemoteWebElement::class); - $element->expects($this->once())->method('getCoordinates')->willReturn($coord); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'cart' === $selector->getValue(); - }))->willReturn($element); - $mouse = $this->createMock(RemoteMouse::class); - $mouse->expects($this->once())->method('mouseDown')->with($coord); - $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testMouseDownAt(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::MOUSE_DOWN_AT); - $command->setTarget('id=cart'); - $command->setValue('5,10'); - $coord = $this->createMock(WebDriverCoordinates::class); - $element = $this->createMock(RemoteWebElement::class); - $element->expects($this->once())->method('getCoordinates')->willReturn($coord); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'cart' === $selector->getValue(); - }))->willReturn($element); - $mouse = $this->createMock(RemoteMouse::class); - $mouse->expects($this->once())->method('mouseMove')->with($coord, 5, 10)->willReturnSelf(); - $mouse->expects($this->once())->method('mouseDown'); - $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testMouseMoveAt(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::MOUSE_MOVE_AT); - $command->setTarget('id=cart'); - $command->setValue('5,10'); - $coord = $this->createMock(WebDriverCoordinates::class); - $element = $this->createMock(RemoteWebElement::class); - $element->expects($this->once())->method('getCoordinates')->willReturn($coord); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'cart' === $selector->getValue(); - }))->willReturn($element); - $mouse = $this->createMock(RemoteMouse::class); - $mouse->expects($this->once())->method('mouseMove')->with($coord, 5, 10); - $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testMouseOutTop(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::MOUSE_OUT); - $command->setTarget('id=cart'); - $coord = $this->createMock(WebDriverCoordinates::class); - $element = $this->createMock(RemoteWebElement::class); - $element->expects($this->once())->method('getCoordinates')->willReturn($coord); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'cart' === $selector->getValue(); - }))->willReturn($element); - $mouse = $this->createMock(RemoteMouse::class); - $mouse->expects($this->once())->method('mouseMove')->with($coord, null, -2); - $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); - $this->driver->expects($this->once())->method('executeScript')->with( - 'return [arguments[0].getBoundingClientRect(), {height: window.innerHeight, width: window.innerWidth}];', - [$element] - )->willReturn([ - $rect = (object) ['top' => 1, 'height' => 2], - $vp = (object) [], - ]); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testMouseOutRight(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::MOUSE_OUT); - $command->setTarget('id=cart'); - $coord = $this->createMock(WebDriverCoordinates::class); - $element = $this->createMock(RemoteWebElement::class); - $element->expects($this->once())->method('getCoordinates')->willReturn($coord); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'cart' === $selector->getValue(); - }))->willReturn($element); - $mouse = $this->createMock(RemoteMouse::class); - $mouse->expects($this->once())->method('mouseMove')->with($coord, 2); - $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); - $this->driver->expects($this->once())->method('executeScript')->with( - 'return [arguments[0].getBoundingClientRect(), {height: window.innerHeight, width: window.innerWidth}];', - [$element] - )->willReturn([ - $rect = (object) ['top' => 0, 'right' => 2], - $vp = (object) ['width' => 3], - ]); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testMouseOutBottom(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::MOUSE_OUT); - $command->setTarget('id=cart'); - $coord = $this->createMock(WebDriverCoordinates::class); - $element = $this->createMock(RemoteWebElement::class); - $element->expects($this->once())->method('getCoordinates')->willReturn($coord); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'cart' === $selector->getValue(); - }))->willReturn($element); - $mouse = $this->createMock(RemoteMouse::class); - $mouse->expects($this->once())->method('mouseMove')->with($coord, null, 3); - $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); - $this->driver->expects($this->once())->method('executeScript')->with( - 'return [arguments[0].getBoundingClientRect(), {height: window.innerHeight, width: window.innerWidth}];', - [$element] - )->willReturn([ - $rect = (object) ['top' => 0, 'right' => 2, 'bottom' => 1, 'height' => 4], - $vp = (object) ['width' => 2, 'height' => 2], - ]); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testMouseOutLeft(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::MOUSE_OUT); - $command->setTarget('id=cart'); - $coord = $this->createMock(WebDriverCoordinates::class); - $element = $this->createMock(RemoteWebElement::class); - $element->expects($this->once())->method('getCoordinates')->willReturn($coord); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'cart' === $selector->getValue(); - }))->willReturn($element); - $mouse = $this->createMock(RemoteMouse::class); - $mouse->expects($this->once())->method('mouseMove')->with($coord, -1); - $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); - $this->driver->expects($this->once())->method('executeScript')->with( - 'return [arguments[0].getBoundingClientRect(), {height: window.innerHeight, width: window.innerWidth}];', - [$element] - )->willReturn([ - $rect = (object) ['top' => 0, 'right' => 2, 'bottom' => 1, 'left' => 1], - $vp = (object) ['width' => 2, 'height' => 1], - ]); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testUnableMouseOut(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::MOUSE_OUT); - $command->setTarget('id=cart'); - $element = $this->createMock(RemoteWebElement::class); - $element->expects($this->never())->method('getCoordinates'); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'cart' === $selector->getValue(); - }))->willReturn($element); - $this->driver->expects($this->never())->method('getMouse'); - $this->driver->expects($this->once())->method('executeScript')->with( - 'return [arguments[0].getBoundingClientRect(), {height: window.innerHeight, width: window.innerWidth}];', - [$element] - )->willReturn([ - $rect = (object) ['top' => 0, 'right' => 2, 'bottom' => 1, 'left' => 0], - $vp = (object) ['width' => 2, 'height' => 1], - ]); - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Unable to perform mouse out as the element takes up the entire viewport'); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testMouseOver(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::MOUSE_OVER); - $command->setTarget('id=cart'); - $coord = $this->createMock(WebDriverCoordinates::class); - $element = $this->createMock(RemoteWebElement::class); - $element->expects($this->once())->method('getCoordinates')->willReturn($coord); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'cart' === $selector->getValue(); - }))->willReturn($element); - $mouse = $this->createMock(RemoteMouse::class); - $mouse->expects($this->once())->method('mouseMove')->with($coord); - $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testMouseUp(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::MOUSE_UP); - $command->setTarget('id=cart'); - $coord = $this->createMock(WebDriverCoordinates::class); - $element = $this->createMock(RemoteWebElement::class); - $element->expects($this->once())->method('getCoordinates')->willReturn($coord); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'cart' === $selector->getValue(); - }))->willReturn($element); - $mouse = $this->createMock(RemoteMouse::class); - $mouse->expects($this->once())->method('mouseUp')->with($coord); - $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testMouseUpAt(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::MOUSE_UP_AT); - $command->setTarget('id=cart'); - $command->setValue('5,10'); - $coord = $this->createMock(WebDriverCoordinates::class); - $element = $this->createMock(RemoteWebElement::class); - $element->expects($this->once())->method('getCoordinates')->willReturn($coord); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'cart' === $selector->getValue(); - }))->willReturn($element); - $mouse = $this->createMock(RemoteMouse::class); - $mouse->expects($this->once())->method('mouseMove')->with($coord, 5, 10)->willReturnSelf(); - $mouse->expects($this->once())->method('mouseUp'); - $this->driver->expects($this->once())->method('getMouse')->willReturn($mouse); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testSelect(): void - { - $command = new Command(); - $command->setCommand(MouseCommandRunner::SELECT); - $command->setTarget('partialLinkText=Language'); - $command->setValue('css=option[value=en_US]'); - $select = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'partial link text' === $selector->getMechanism() - && 'Language' === $selector->getValue(); - }))->willReturn($select); - $option = $this->createMock(WebDriverElement::class); - $option->expects($this->once())->method('click'); - $select->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && 'option[value=en_US]' === $selector->getValue(); - }))->willReturn($option); - $this->runner->run($command, $this->values, $this->driver); - } - - public function checkDataProvider(): array - { - return [ - [true, false], - [false, true], - ]; - } - - public function uncheckDataProvider(): array - { - return [ - [true, true], - [false, false], - ]; - } - - public function targetProvider(): array - { - return [ - [MouseCommandRunner::CLICK, null, false], - [MouseCommandRunner::CLICK, 'anything', false], - [MouseCommandRunner::CLICK, 'xpath=//path/to/element', true], - ]; - } - - public function commandsRequireTarget(): array - { - return [ - MouseCommandRunner::ADD_SELECTION, - MouseCommandRunner::REMOVE_SELECTION, - MouseCommandRunner::CHECK, - MouseCommandRunner::UNCHECK, - MouseCommandRunner::CLICK, - MouseCommandRunner::CLICK_AT, - MouseCommandRunner::DOUBLE_CLICK, - MouseCommandRunner::DOUBLE_CLICK_AT, - MouseCommandRunner::DRAG_AND_DROP_TO_OBJECT, - MouseCommandRunner::MOUSE_DOWN, - MouseCommandRunner::MOUSE_DOWN_AT, - MouseCommandRunner::MOUSE_MOVE_AT, - MouseCommandRunner::MOUSE_OUT, - MouseCommandRunner::MOUSE_OVER, - MouseCommandRunner::MOUSE_UP, - MouseCommandRunner::MOUSE_UP_AT, - MouseCommandRunner::SELECT, - ]; - } - - public function commandsRequireValue(): array - { - return [ - MouseCommandRunner::ADD_SELECTION, - MouseCommandRunner::REMOVE_SELECTION, - MouseCommandRunner::CLICK_AT, - MouseCommandRunner::DOUBLE_CLICK_AT, - MouseCommandRunner::DRAG_AND_DROP_TO_OBJECT, - MouseCommandRunner::MOUSE_DOWN_AT, - MouseCommandRunner::MOUSE_MOVE_AT, - MouseCommandRunner::MOUSE_UP_AT, - MouseCommandRunner::SELECT, - ]; - } -} diff --git a/tests/Command/Runner/RunnerTestCase.php b/tests/Command/Runner/RunnerTestCase.php deleted file mode 100644 index a88e1592..00000000 --- a/tests/Command/Runner/RunnerTestCase.php +++ /dev/null @@ -1,74 +0,0 @@ -driver = $this->createMock(RemoteWebDriver::class); - $this->runner = $this->createRunner(); - $this->values = $this->createMock(ValuesInterface::class); - $this->element = $this->createMock(WebDriverElement::class); - } - - abstract protected function createRunner(): CommandRunner; - - /** - * @dataProvider supportsCommandProvider - */ - public function testSupports(string $commandAsString, bool $supports): void - { - $command = new Command(); - $command->setCommand($commandAsString); - $this->assertSame($supports, $this->runner->supports($command)); - } - - public function supportsCommandProvider(): array - { - return [ - ...array_map(fn ($command) => [$command, true], $this->createRunner()->getAllCommands()), - ['invalidCommand', false], - ]; - } - - /** - * @dataProvider targetProvider - */ - public function testValidateTarget(string $commandString, $target, bool $valid): void - { - $command = new Command(); - $command->setCommand($commandString); - $command->setTarget($target); - $this->assertSame($valid, $this->runner->validateTarget($command)); - } - - abstract public function targetProvider(): array; - - public function testGetCommandsRequireTarget(): void - { - $this->assertSame($this->commandsRequireTarget(), $this->runner->getCommandsRequireTarget()); - } - - abstract public function commandsRequireTarget(): array; - - public function testGetCommandsRequireValue(): void - { - $this->assertSame($this->commandsRequireValue(), $this->runner->getCommandsRequireValue()); - } - - abstract public function commandsRequireValue(): array; -} diff --git a/tests/Command/Runner/ScriptCommandRunnerTest.php b/tests/Command/Runner/ScriptCommandRunnerTest.php deleted file mode 100644 index cdfa8625..00000000 --- a/tests/Command/Runner/ScriptCommandRunnerTest.php +++ /dev/null @@ -1,88 +0,0 @@ -setCommand(ScriptCommandRunner::RUN_SCRIPT); - $command->setTarget('alert("Hello World!")'); - $this->values->expects($this->never())->method('getValues'); - $this->driver->expects($this->once())->method('executeScript')->with( - 'alert("Hello World!")', - [], - ); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testExecuteScript(): void - { - $command = new Command(); - $command->setCommand(ScriptCommandRunner::EXECUTE_SCRIPT); - $command->setTarget('return 2 + 1;'); - $command->setValue('total'); - $this->values->expects($this->never())->method('getValues'); - $this->values->expects($this->once())->method('setValue')->with('total', 3); - $this->driver->expects($this->once())->method('executeScript')->with( - 'return 2 + 1;', - [], - )->willReturn(3); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testExecuteAsyncScript(): void - { - $command = new Command(); - $command->setCommand(ScriptCommandRunner::EXECUTE_ASYNC_SCRIPT); - $command->setTarget('window.setTimeout(function() { return "Hello";}, 1000);'); - $command->setValue('message'); - $this->values->expects($this->never())->method('getValues'); - $this->values->expects($this->once())->method('setValue')->with('message', 'Hello'); - $this->driver->expects($this->once())->method('executeAsyncScript')->with( - 'window.setTimeout(function() { return "Hello";}, 1000);', - [], - )->willReturn('Hello'); - $this->runner->run($command, $this->values, $this->driver); - } - - public function targetProvider(): array - { - return [ - [ScriptCommandRunner::RUN_SCRIPT, 'anything', true], - ]; - } - - public function commandsRequireTarget(): array - { - return [ - ScriptCommandRunner::RUN_SCRIPT, - ScriptCommandRunner::EXECUTE_SCRIPT, - ScriptCommandRunner::EXECUTE_ASYNC_SCRIPT, - ]; - } - - public function commandsRequireValue(): array - { - return [ - ScriptCommandRunner::EXECUTE_SCRIPT, - ScriptCommandRunner::EXECUTE_ASYNC_SCRIPT, - ]; - } -} diff --git a/tests/Command/Runner/StoreCommandRunnerTest.php b/tests/Command/Runner/StoreCommandRunnerTest.php deleted file mode 100644 index 6e4f914a..00000000 --- a/tests/Command/Runner/StoreCommandRunnerTest.php +++ /dev/null @@ -1,174 +0,0 @@ -setCommand(StoreCommandRunner::STORE); - $command->setTarget('1'); - $command->setValue('count'); - $this->values->expects($this->once())->method('setValue')->with('count', '1'); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testStoreAttribute(): void - { - $command = new Command(); - $command->setCommand(StoreCommandRunner::STORE_ATTRIBUTE); - $command->setTarget('css=.readmore@href'); - $command->setValue('readmoreLink'); - $this->values->expects($this->once())->method('setValue')->with('readmoreLink', 'http://example.com'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('getAttribute')->with('href')->willReturn('http://example.com'); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && '.readmore' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testStoreElementCount(): void - { - $command = new Command(); - $command->setCommand(StoreCommandRunner::STORE_ELEMENT_COUNT); - $command->setTarget('css=.item'); - $command->setValue('itemCount'); - $this->values->expects($this->once())->method('setValue')->with('itemCount', 2); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElements')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && '.item' === $selector->getValue(); - }))->willReturn([$element, $element]); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testStoreJson(): void - { - $command = new Command(); - $command->setCommand(StoreCommandRunner::STORE_JSON); - $command->setTarget('{ "items": [1, 2, 3] }'); - $command->setValue('json'); - $this->values->expects($this->once())->method('setValue')->with( - 'json', - $this->callback(fn ($object) => $object instanceof \stdClass && $object->items === [1, 2, 3]) - ); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testStoreText(): void - { - $command = new Command(); - $command->setCommand(StoreCommandRunner::STORE_TEXT); - $command->setTarget('css=.head-line'); - $command->setValue('headLine'); - $this->values->expects($this->once())->method('setValue')->with('headLine', 'Welcome to our site'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('getText')->willReturn('Welcome to our site'); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && '.head-line' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testStoreTitle(): void - { - $command = new Command(); - $command->setCommand(StoreCommandRunner::STORE_TITLE); - $command->setTarget('title'); - $this->values->expects($this->once())->method('setValue')->with('title', 'Welcome'); - $this->driver->expects($this->once())->method('getTitle')->willReturn('Welcome'); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testStoreValue(): void - { - $command = new Command(); - $command->setCommand(StoreCommandRunner::STORE_VALUE); - $command->setTarget('css=.age'); - $command->setValue('age'); - $this->values->expects($this->once())->method('setValue')->with('age', 23); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('getAttribute')->with('value')->willReturn(23); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && '.age' === $selector->getValue(); - }))->willReturn($element); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testStoreWindowHandle(): void - { - $command = new Command(); - $command->setCommand(StoreCommandRunner::STORE_WINDOW_HANDLE); - $command->setTarget('windowHandle'); - $this->values->expects($this->once())->method('setValue')->with('windowHandle', 'window-123'); - $this->driver->expects($this->once())->method('getWindowHandle')->willReturn('window-123'); - $this->runner->run($command, $this->values, $this->driver); - } - - public function targetProvider(): array - { - return [ - [StoreCommandRunner::STORE_ATTRIBUTE, null, false], - [StoreCommandRunner::STORE_ATTRIBUTE, 'anything', false], - [StoreCommandRunner::STORE_ATTRIBUTE, 'xpath=//path/to/element@attribute', true], - [StoreCommandRunner::STORE_TEXT, null, false], - [StoreCommandRunner::STORE_TEXT, 'anything', false], - [StoreCommandRunner::STORE_TEXT, 'xpath=//path/to/element', true], - [StoreCommandRunner::STORE_JSON, null, false], - [StoreCommandRunner::STORE_JSON, 'anything', false], - [StoreCommandRunner::STORE_JSON, '{"key": "value"}', true], - ]; - } - - public function commandsRequireTarget(): array - { - return [ - StoreCommandRunner::STORE_ATTRIBUTE, - StoreCommandRunner::STORE_ELEMENT_COUNT, - StoreCommandRunner::STORE_JSON, - StoreCommandRunner::STORE_TEXT, - StoreCommandRunner::STORE_TITLE, - StoreCommandRunner::STORE_VALUE, - StoreCommandRunner::STORE_WINDOW_HANDLE, - ]; - } - - public function commandsRequireValue(): array - { - return [ - StoreCommandRunner::STORE, - StoreCommandRunner::STORE_ATTRIBUTE, - StoreCommandRunner::STORE_ELEMENT_COUNT, - StoreCommandRunner::STORE_JSON, - StoreCommandRunner::STORE_TEXT, - StoreCommandRunner::STORE_TITLE, - StoreCommandRunner::STORE_VALUE, - ]; - } -} diff --git a/tests/Command/Runner/WaitCommandRunnerTest.php b/tests/Command/Runner/WaitCommandRunnerTest.php deleted file mode 100644 index 2f6d9e32..00000000 --- a/tests/Command/Runner/WaitCommandRunnerTest.php +++ /dev/null @@ -1,212 +0,0 @@ -setCommand(WaitCommandRunner::WAIT_FOR_ELEMENT_EDITABLE); - $command->setTarget('id=name'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'name' === $selector->getValue(); - }))->willReturn($element); - $wait = $this->createMock(WebDriverWait::class); - $wait->expects($this->once())->method('until')->with($this->callback(function ($condition) { - return is_callable($condition) - && $condition instanceof Closure - && $condition(); - })); - $this->driver->expects($this->once())->method('wait')->willReturn($wait); - $this->driver - ->expects($this->once()) - ->method('executeScript') - ->with('return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', [$element]) - ->willReturn((object) ['enabled' => true, 'readonly' => false]); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testWaitForElementNotEditable(): void - { - $command = new Command(); - $command->setCommand(WaitCommandRunner::WAIT_FOR_ELEMENT_NOT_EDITABLE); - $command->setTarget('id=avatar'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'avatar' === $selector->getValue(); - }))->willReturn($element); - $wait = $this->createMock(WebDriverWait::class); - $wait->expects($this->once())->method('until')->with($this->callback(function ($condition) { - return is_callable($condition) - && $condition instanceof Closure - && $condition(); - })); - $this->driver->expects($this->once())->method('wait')->willReturn($wait); - $this->driver - ->expects($this->once()) - ->method('executeScript') - ->with('return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', [$element]) - ->willReturn((object) ['enabled' => false, 'readonly' => true]); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testWaitForElementPresent(): void - { - $command = new Command(); - $command->setCommand(WaitCommandRunner::WAIT_FOR_ELEMENT_PRESENT); - $command->setTarget('id=title'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'title' === $selector->getValue(); - }))->willReturn($element); - $wait = $this->createMock(WebDriverWait::class); - $wait->expects($this->once())->method('until')->with($this->callback(function ($condition) { - return $condition instanceof WebDriverExpectedCondition - && call_user_func($condition->getApply(), $this->driver); - })); - $this->driver->expects($this->once())->method('wait')->willReturn($wait); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testNoElementsPresent(): void - { - $command = new Command(); - $command->setCommand(WaitCommandRunner::WAIT_FOR_ELEMENT_NOT_PRESENT); - $command->setTarget('css=button'); - $this->driver->expects($this->once())->method('findElements')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && 'button' === $selector->getValue(); - }))->willReturn([]); - $this->driver->expects($this->never())->method('wait'); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testWaitForElementNotPresent(): void - { - $command = new Command(); - $command->setCommand(WaitCommandRunner::WAIT_FOR_ELEMENT_NOT_PRESENT); - $command->setTarget('css=button'); - $element = $this->createMock(WebDriverElement::class); - $element - ->expects($this->once()) - ->method('isEnabled') - ->willThrowException(new StaleElementReferenceException('Element gone')); - $this->driver->expects($this->once())->method('findElements')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'css selector' === $selector->getMechanism() - && 'button' === $selector->getValue(); - }))->willReturn([$element]); - $wait = $this->createMock(WebDriverWait::class); - $wait->expects($this->once())->method('until')->with($this->callback(function ($condition) { - return $condition instanceof WebDriverExpectedCondition - && call_user_func($condition->getApply(), $this->driver); - })); - $this->driver->expects($this->once())->method('wait')->willReturn($wait); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testWaitForElementVisible(): void - { - $command = new Command(); - $command->setCommand(WaitCommandRunner::WAIT_FOR_ELEMENT_VISIBLE); - $command->setTarget('id=title'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('isDisplayed')->willReturn(true); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'title' === $selector->getValue(); - }))->willReturn($element); - $wait = $this->createMock(WebDriverWait::class); - $wait->expects($this->once())->method('until')->with($this->callback(function ($condition) { - return $condition instanceof WebDriverExpectedCondition - && call_user_func($condition->getApply(), $this->driver); - })); - $this->driver->expects($this->once())->method('wait')->willReturn($wait); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testWaitForElementNotVisible(): void - { - $command = new Command(); - $command->setCommand(WaitCommandRunner::WAIT_FOR_ELEMENT_NOT_VISIBLE); - $command->setTarget('id=title'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('isDisplayed')->willReturn(false); - $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'id' === $selector->getMechanism() - && 'title' === $selector->getValue(); - }))->willReturn($element); - $wait = $this->createMock(WebDriverWait::class); - $wait->expects($this->once())->method('until')->with($this->callback(function ($condition) { - return $condition instanceof WebDriverExpectedCondition - && call_user_func($condition->getApply(), $this->driver); - })); - $this->driver->expects($this->once())->method('wait')->willReturn($wait); - $this->runner->run($command, $this->values, $this->driver); - } - - public function targetProvider(): array - { - return [ - [WaitCommandRunner::WAIT_FOR_ELEMENT_VISIBLE, null, false], - [WaitCommandRunner::WAIT_FOR_ELEMENT_VISIBLE, 'anything', false], - [WaitCommandRunner::WAIT_FOR_ELEMENT_VISIBLE, 'xpath=//path/to/element', true], - ]; - } - - public function commandsRequireTarget(): array - { - return [ - WaitCommandRunner::WAIT_FOR_ELEMENT_EDITABLE, - WaitCommandRunner::WAIT_FOR_ELEMENT_NOT_EDITABLE, - WaitCommandRunner::WAIT_FOR_ELEMENT_PRESENT, - WaitCommandRunner::WAIT_FOR_ELEMENT_NOT_PRESENT, - WaitCommandRunner::WAIT_FOR_ELEMENT_VISIBLE, - WaitCommandRunner::WAIT_FOR_ELEMENT_NOT_VISIBLE, - ]; - } - - public function commandsRequireValue(): array - { - return [ - WaitCommandRunner::WAIT_FOR_ELEMENT_EDITABLE, - WaitCommandRunner::WAIT_FOR_ELEMENT_NOT_EDITABLE, - WaitCommandRunner::WAIT_FOR_ELEMENT_PRESENT, - WaitCommandRunner::WAIT_FOR_ELEMENT_NOT_PRESENT, - WaitCommandRunner::WAIT_FOR_ELEMENT_VISIBLE, - WaitCommandRunner::WAIT_FOR_ELEMENT_NOT_VISIBLE, - ]; - } -} diff --git a/tests/Command/Runner/WindowCommandRunnerTest.php b/tests/Command/Runner/WindowCommandRunnerTest.php deleted file mode 100644 index dfbf5ff1..00000000 --- a/tests/Command/Runner/WindowCommandRunnerTest.php +++ /dev/null @@ -1,166 +0,0 @@ -setCommand(WindowCommandRunner::OPEN); - $command->setTarget('https://demo.sylius.com/en_US/'); - $this->driver->expects($this->once())->method('get')->with('https://demo.sylius.com/en_US/'); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testSetWindowSize(): void - { - $command = new Command(); - $command->setCommand(WindowCommandRunner::SET_WINDOW_SIZE); - $command->setTarget('1280x800'); - $window = $this->createMock(WebDriverWindow::class); - $window->expects($this->once())->method('setSize')->with($this->callback(function ($dimention) { - return $dimention instanceof WebDriverDimension - && 1280 === $dimention->getWidth() - && 800 === $dimention->getHeight(); - })); - $options = $this->createMock(WebDriverOptions::class); - $options->expects($this->once())->method('window')->willReturn($window); - $this->driver->expects($this->once())->method('manage')->willReturn($options); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testSelectWindow(): void - { - $command = new Command(); - $command->setCommand(WindowCommandRunner::SELECT_WINDOW); - $command->setTarget('handle=testing'); - $targetLocator = $this->createMock(RemoteTargetLocator::class); - $targetLocator->expects($this->once())->method('window')->with('testing'); - $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testClose(): void - { - $command = new Command(); - $command->setCommand(WindowCommandRunner::CLOSE); - $this->driver->expects($this->once())->method('close'); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testSelectFrameRelativeTop(): void - { - $command = new Command(); - $command->setCommand(WindowCommandRunner::SELECT_FRAME); - $command->setTarget('relative=top'); - $targetLocator = $this->createMock(RemoteTargetLocator::class); - $targetLocator->expects($this->once())->method('defaultContent'); - $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testSelectFrameRelativeParent(): void - { - $command = new Command(); - $command->setCommand(WindowCommandRunner::SELECT_FRAME); - $command->setTarget('relative=parent'); - $targetLocator = $this->createMock(RemoteTargetLocator::class); - $targetLocator->expects($this->once())->method('parent'); - $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testSelectFrameIndex(): void - { - $command = new Command(); - $command->setCommand(WindowCommandRunner::SELECT_FRAME); - $command->setTarget('index=123'); - $targetLocator = $this->createMock(RemoteTargetLocator::class); - $targetLocator->expects($this->once())->method('frame')->with(123); - $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); - $this->runner->run($command, $this->values, $this->driver); - } - - public function testSelectFrameSelector(): void - { - $command = new Command(); - $command->setCommand(WindowCommandRunner::SELECT_FRAME); - $command->setTarget('linkText=Read More'); - $element = $this->createMock(WebDriverElement::class); - $this->driver->expects($this->exactly(2))->method('findElement')->with($this->callback(function ($selector) { - return $selector instanceof WebDriverBy - && 'link text' === $selector->getMechanism() - && 'Read More' === $selector->getValue(); - }))->willReturn($element); - $targetLocator = $this->createMock(RemoteTargetLocator::class); - $targetLocator->expects($this->once())->method('frame')->with($element); - $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); - $wait = $this->createMock(WebDriverWait::class); - $wait->expects($this->once())->method('until')->with($this->callback(function ($condition) { - return $condition instanceof WebDriverExpectedCondition - && call_user_func($condition->getApply(), $this->driver); - })); - $this->driver->expects($this->once())->method('wait')->willReturn($wait); - $this->runner->run($command, $this->values, $this->driver); - } - - public function targetProvider(): array - { - return [ - [WindowCommandRunner::OPEN, null, false], - [WindowCommandRunner::OPEN, 'anything', false], - [WindowCommandRunner::OPEN, 'http://example.com', true], - [WindowCommandRunner::SET_WINDOW_SIZE, null, false], - [WindowCommandRunner::SET_WINDOW_SIZE, 'anything', false], - [WindowCommandRunner::SET_WINDOW_SIZE, '123x234', true], - [WindowCommandRunner::SELECT_WINDOW, null, false], - [WindowCommandRunner::SELECT_WINDOW, 'anything', false], - [WindowCommandRunner::SELECT_WINDOW, 'handle=anything', true], - [WindowCommandRunner::SELECT_FRAME, null, false], - [WindowCommandRunner::SELECT_FRAME, 'anything', false], - [WindowCommandRunner::SELECT_FRAME, 'relative=top', true], - [WindowCommandRunner::SELECT_FRAME, 'relative=parent', true], - [WindowCommandRunner::SELECT_FRAME, 'index=123', true], - [WindowCommandRunner::SELECT_FRAME, 'xpath=//path/to/element', true], - ]; - } - - public function commandsRequireTarget(): array - { - return [ - WindowCommandRunner::OPEN, - WindowCommandRunner::SET_WINDOW_SIZE, - WindowCommandRunner::SELECT_WINDOW, - WindowCommandRunner::SELECT_FRAME, - ]; - } - - public function commandsRequireValue(): array - { - return []; - } -} diff --git a/tests/Command/Script/ExecuteAsyncScriptCommandTest.php b/tests/Command/Script/ExecuteAsyncScriptCommandTest.php new file mode 100644 index 00000000..7cad1c49 --- /dev/null +++ b/tests/Command/Script/ExecuteAsyncScriptCommandTest.php @@ -0,0 +1,74 @@ +values->expects($this->never())->method('getValues'); + if ($value) { + $this->values->expects($this->once())->method('setValue')->with($value, 'Hello'); + } else { + $this->values->expects($this->never())->method('setValue'); + } + $this->driver->expects($this->once())->method('executeAsyncScript')->with( + 'window.setTimeout(function() { return "Hello";}, 1000);', + [], + )->willReturn('Hello'); + $this->command->run( + 'window.setTimeout(function() { return "Hello";}, 1000);', + $value, + $this->values, + $this->driver, + ); + } + + public function runProvider(): array + { + return [ + [null], + ['message'], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Script/ExecuteScriptCommandTest.php b/tests/Command/Script/ExecuteScriptCommandTest.php new file mode 100644 index 00000000..a7352e8c --- /dev/null +++ b/tests/Command/Script/ExecuteScriptCommandTest.php @@ -0,0 +1,69 @@ +values->expects($this->never())->method('getValues'); + if ($value) { + $this->values->expects($this->once())->method('setValue')->with($value, 3); + } else { + $this->values->expects($this->never())->method('setValue'); + } + $this->driver->expects($this->once())->method('executeScript')->with( + 'return 2 + 1;', + [], + )->willReturn(3); + $this->command->run('return 2 + 1;', $value, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + [null], + ['total'], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Script/RunScriptCommandTest.php b/tests/Command/Script/RunScriptCommandTest.php new file mode 100644 index 00000000..85f561d2 --- /dev/null +++ b/tests/Command/Script/RunScriptCommandTest.php @@ -0,0 +1,53 @@ +values->expects($this->never())->method('getValues'); + $this->driver->expects($this->once())->method('executeScript')->with( + 'alert("Hello World!")', + [], + ); + $this->command->run('alert("Hello World!")', null, $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Store/StoreAttributeCommandTest.php b/tests/Command/Store/StoreAttributeCommandTest.php new file mode 100644 index 00000000..5479d4c4 --- /dev/null +++ b/tests/Command/Store/StoreAttributeCommandTest.php @@ -0,0 +1,72 @@ +values + ->expects($this->once()) + ->method('setValue') + ->with('readmoreLink', 'http://example.com'); + $element = $this->createMock(WebDriverElement::class); + $element + ->expects($this->once()) + ->method('getAttribute') + ->with('href') + ->willReturn('http://example.com'); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'css selector' === $selector->getMechanism() + && '.readmore' === $selector->getValue(); + })) + ->willReturn($element); + $this->command->run('css=.readmore@href', 'readmoreLink', $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['key@value', false], + ['css=#selector', false], + ['css=#selector@attr', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Store/StoreCommandTest.php b/tests/Command/Store/StoreCommandTest.php new file mode 100644 index 00000000..bfb80c9e --- /dev/null +++ b/tests/Command/Store/StoreCommandTest.php @@ -0,0 +1,49 @@ +values->expects($this->once())->method('setValue')->with('count', '1'); + $this->command->run('1', 'count', $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Store/StoreElementCountCommandTest.php b/tests/Command/Store/StoreElementCountCommandTest.php new file mode 100644 index 00000000..fdafdef4 --- /dev/null +++ b/tests/Command/Store/StoreElementCountCommandTest.php @@ -0,0 +1,58 @@ +values->expects($this->once())->method('setValue')->with('itemCount', 2); + $element = $this->createMock(WebDriverElement::class); + $this->driver->expects($this->once())->method('findElements')->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'css selector' === $selector->getMechanism() + && '.item' === $selector->getValue(); + }))->willReturn([$element, $element]); + $this->command->run('css=.item', 'itemCount', $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Store/StoreJsonCommandTest.php b/tests/Command/Store/StoreJsonCommandTest.php new file mode 100644 index 00000000..85d43792 --- /dev/null +++ b/tests/Command/Store/StoreJsonCommandTest.php @@ -0,0 +1,53 @@ +values->expects($this->once())->method('setValue')->with( + 'json', + $this->callback(fn ($object) => $object instanceof \stdClass && $object->items === [1, 2, 3]) + ); + $this->command->run('{ "items": [1, 2, 3] }', 'json', $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['{"key": "value"}', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Store/StoreTextCommandTest.php b/tests/Command/Store/StoreTextCommandTest.php new file mode 100644 index 00000000..d39f14d2 --- /dev/null +++ b/tests/Command/Store/StoreTextCommandTest.php @@ -0,0 +1,66 @@ +values + ->expects($this->once()) + ->method('setValue') + ->with('headLine', 'Welcome to our site'); + $element = $this->createMock(WebDriverElement::class); + $element->expects($this->once())->method('getText')->willReturn('Welcome to our site'); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'css selector' === $selector->getMechanism() + && '.head-line' === $selector->getValue(); + })) + ->willReturn($element); + $this->command->run('css=.head-line', 'headLine', $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Store/StoreTitleCommandTest.php b/tests/Command/Store/StoreTitleCommandTest.php new file mode 100644 index 00000000..741a7d07 --- /dev/null +++ b/tests/Command/Store/StoreTitleCommandTest.php @@ -0,0 +1,50 @@ +values->expects($this->once())->method('setValue')->with('title', 'Welcome'); + $this->driver->expects($this->once())->method('getTitle')->willReturn('Welcome'); + $this->command->run('title', null, $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Store/StoreValueCommandTest.php b/tests/Command/Store/StoreValueCommandTest.php new file mode 100644 index 00000000..e47bb7f5 --- /dev/null +++ b/tests/Command/Store/StoreValueCommandTest.php @@ -0,0 +1,63 @@ +values->expects($this->once())->method('setValue')->with('age', 23); + $element = $this->createMock(WebDriverElement::class); + $element->expects($this->once())->method('getAttribute')->with('value')->willReturn(23); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'css selector' === $selector->getMechanism() + && '.age' === $selector->getValue(); + })) + ->willReturn($element); + $this->command->run('css=.age', 'age', $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Store/StoreWindowHandleCommandTest.php b/tests/Command/Store/StoreWindowHandleCommandTest.php new file mode 100644 index 00000000..d33acf63 --- /dev/null +++ b/tests/Command/Store/StoreWindowHandleCommandTest.php @@ -0,0 +1,50 @@ +values->expects($this->once())->method('setValue')->with('windowHandle', 'window-123'); + $this->driver->expects($this->once())->method('getWindowHandle')->willReturn('window-123'); + $this->command->run('windowHandle', null, $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Wait/WaitForElementEditableCommandTest.php b/tests/Command/Wait/WaitForElementEditableCommandTest.php new file mode 100644 index 00000000..4da8613f --- /dev/null +++ b/tests/Command/Wait/WaitForElementEditableCommandTest.php @@ -0,0 +1,66 @@ +createMock(WebDriverElement::class); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'name' === $selector->getValue(); + })) + ->willReturn($element); + $wait = $this->createMock(WebDriverWait::class); + $wait + ->expects($this->once()) + ->method('until') + ->with($this->callback(fn ($condition) => is_callable($condition) + && $condition instanceof Closure + && $editable === $condition()), 'Timed out waiting for element to be editable'); + $this->driver->expects($this->once())->method('wait')->with(123)->willReturn($wait); + $this->driver + ->expects($this->once()) + ->method('executeScript') + ->with( + 'return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', + [$element] + ) + ->willReturn((object) ['enabled' => $enabled, 'readonly' => $readonly]); + $this->command->run('id=name', 123, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + [true, false, true], + [true, true, false], + [false, true, false], + [false, false, false], + ]; + } +} diff --git a/tests/Command/Wait/WaitForElementNotEditableCommandTest.php b/tests/Command/Wait/WaitForElementNotEditableCommandTest.php new file mode 100644 index 00000000..f7dc8e95 --- /dev/null +++ b/tests/Command/Wait/WaitForElementNotEditableCommandTest.php @@ -0,0 +1,66 @@ +createMock(WebDriverElement::class); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'name' === $selector->getValue(); + })) + ->willReturn($element); + $wait = $this->createMock(WebDriverWait::class); + $wait + ->expects($this->once()) + ->method('until') + ->with($this->callback(fn ($condition) => is_callable($condition) + && $condition instanceof Closure + && !$editable === $condition()), 'Timed out waiting for element to not be editable'); + $this->driver->expects($this->once())->method('wait')->with(123)->willReturn($wait); + $this->driver + ->expects($this->once()) + ->method('executeScript') + ->with( + 'return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', + [$element] + ) + ->willReturn((object) ['enabled' => $enabled, 'readonly' => $readonly]); + $this->command->run('id=name', 123, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + [true, false, true], + [true, true, false], + [false, true, false], + [false, false, false], + ]; + } +} diff --git a/tests/Command/Wait/WaitForElementNotPresentCommandTest.php b/tests/Command/Wait/WaitForElementNotPresentCommandTest.php new file mode 100644 index 00000000..134c768c --- /dev/null +++ b/tests/Command/Wait/WaitForElementNotPresentCommandTest.php @@ -0,0 +1,72 @@ +createMock(WebDriverElement::class)); + $this->driver + ->expects($this->once()) + ->method('findElements') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'css selector' === $selector->getMechanism() + && 'button' === $selector->getValue(); + })) + ->willReturn($elements); + if ($elementsCount > 0) { + $mock = $elements[0] + ->expects($this->once()) + ->method('isEnabled'); + if ($stale) { + $mock->willThrowException(new StaleElementReferenceException('Element gone')); + } + $wait = $this->createMock(WebDriverWait::class); + $wait + ->expects($this->once()) + ->method('until') + ->with( + $this->callback(function (WebDriverExpectedCondition $condition) use ($stale) { + return $stale === call_user_func($condition->getApply(), $this->driver); + }) + ); + $this->driver->expects($this->once())->method('wait')->with(123)->willReturn($wait); + } else { + $this->driver->expects($this->never())->method('wait'); + } + $this->command->run('css=button', 123, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + [0, false], + [1, false], + [2, false], + [1, true], + [2, true], + ]; + } +} diff --git a/tests/Command/Wait/WaitForElementNotVisibleCommandTest.php b/tests/Command/Wait/WaitForElementNotVisibleCommandTest.php new file mode 100644 index 00000000..e8acc21d --- /dev/null +++ b/tests/Command/Wait/WaitForElementNotVisibleCommandTest.php @@ -0,0 +1,72 @@ +createMock(WebDriverElement::class); + $mock = $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'title' === $selector->getValue(); + })); + if ($present) { + $mock->willReturn($element); + $mock = $element + ->expects($this->once()) + ->method('isDisplayed'); + if ($stale) { + $mock->willThrowException(new StaleElementReferenceException('Element gone')); + } else { + $mock->willReturn($displayed); + } + } else { + $mock->willThrowException(new NoSuchElementException('Element missing')); + } + $wait = $this->createMock(WebDriverWait::class); + $wait + ->expects($this->once()) + ->method('until') + ->with($this->callback(function (WebDriverExpectedCondition $condition) use ($displayed, $present, $stale) { + return (!$displayed || !$present || $stale) === call_user_func($condition->getApply(), $this->driver); + })); + $this->driver->expects($this->once())->method('wait')->with(123)->willReturn($wait); + $this->command->run('id=title', 123, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + [false, true, false], + [true, true, false], + [false, false, false], + [false, true, true], + ]; + } +} diff --git a/tests/Command/Wait/WaitForElementPresentCommandTest.php b/tests/Command/Wait/WaitForElementPresentCommandTest.php new file mode 100644 index 00000000..189588eb --- /dev/null +++ b/tests/Command/Wait/WaitForElementPresentCommandTest.php @@ -0,0 +1,61 @@ +createMock(WebDriverElement::class); + $mock = $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'title' === $selector->getValue(); + })); + if ($present) { + $mock->willReturn($element); + } else { + $mock->willThrowException(new NoSuchElementException('Element missing')); + } + $wait = $this->createMock(WebDriverWait::class); + $wait + ->expects($this->once()) + ->method('until') + ->with($this->callback(function (WebDriverExpectedCondition $condition) use ($present) { + return $present === !empty(call_user_func($condition->getApply(), $this->driver)); + })); + $this->driver->expects($this->once())->method('wait')->with(123)->willReturn($wait); + $this->command->run('id=title', 123, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + [false], + [true], + ]; + } +} diff --git a/tests/Command/Wait/WaitForElementVisibleCommandTest.php b/tests/Command/Wait/WaitForElementVisibleCommandTest.php new file mode 100644 index 00000000..c59176b6 --- /dev/null +++ b/tests/Command/Wait/WaitForElementVisibleCommandTest.php @@ -0,0 +1,66 @@ +createMock(WebDriverElement::class); + $this->driver + ->expects($this->once()) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'id' === $selector->getMechanism() + && 'title' === $selector->getValue(); + })) + ->willReturn($element); + $mock = $element + ->expects($this->once()) + ->method('isDisplayed'); + if ($stale) { + $mock->willThrowException(new StaleElementReferenceException('Element gone')); + } else { + $mock->willReturn($displayed); + } + $wait = $this->createMock(WebDriverWait::class); + $wait + ->expects($this->once()) + ->method('until') + ->with($this->callback(function (WebDriverExpectedCondition $condition) use ($displayed, $stale) { + return ($displayed && !$stale) === !empty(call_user_func($condition->getApply(), $this->driver)); + })); + $this->driver->expects($this->once())->method('wait')->with(123)->willReturn($wait); + $this->command->run('id=title', 123, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + [true, false], + [false, false], + [false, true], + ]; + } +} diff --git a/tests/Command/Wait/WaitTestCase.php b/tests/Command/Wait/WaitTestCase.php new file mode 100644 index 00000000..258730ee --- /dev/null +++ b/tests/Command/Wait/WaitTestCase.php @@ -0,0 +1,34 @@ +driver->expects($this->once())->method('close'); + $this->command->run(null, null, $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Window/OpenCommandTest.php b/tests/Command/Window/OpenCommandTest.php new file mode 100644 index 00000000..23eff101 --- /dev/null +++ b/tests/Command/Window/OpenCommandTest.php @@ -0,0 +1,50 @@ +driver->expects($this->once())->method('get')->with('https://demo.sylius.com/en_US/'); + $this->command->run('https://demo.sylius.com/en_US/', null, $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['http://domain.example/path', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Window/SelectFrameCommandTest.php b/tests/Command/Window/SelectFrameCommandTest.php new file mode 100644 index 00000000..ac4244e3 --- /dev/null +++ b/tests/Command/Window/SelectFrameCommandTest.php @@ -0,0 +1,95 @@ +createMock(WebDriverElement::class); + $this->driver + ->expects($this->exactly(2)) + ->method('findElement') + ->with($this->callback(function ($selector) { + return $selector instanceof WebDriverBy + && 'link text' === $selector->getMechanism() + && 'Read More' === $selector->getValue(); + })) + ->willReturn($element); + $params = [$element]; + $wait = $this->createMock(WebDriverWait::class); + $wait + ->expects($this->once()) + ->method('until') + ->with($this->callback(function ($condition) { + return $condition instanceof WebDriverExpectedCondition + && call_user_func($condition->getApply(), $this->driver); + })); + $this->driver->expects($this->once())->method('wait')->willReturn($wait); + } + $targetLocator = $this->createMock(RemoteTargetLocator::class); + $targetLocator->expects($this->once())->method($method)->with(...$params); + $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); + $this->command->run($target, null, $this->values, $this->driver); + } + + public function runProvider(): array + { + return [ + ['relative=top', 'defaultContent', []], + ['relative=parent', 'parent', []], + ['index=123', 'frame', [123]], + ['linkText=Read More', 'frame', null], + ]; + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['relative=top', true], + ['relative=parent', true], + ['index=123', true], + ['css=#selector', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Window/SelectWindowCommandTest.php b/tests/Command/Window/SelectWindowCommandTest.php new file mode 100644 index 00000000..d8537cb5 --- /dev/null +++ b/tests/Command/Window/SelectWindowCommandTest.php @@ -0,0 +1,54 @@ +createMock(RemoteTargetLocator::class); + $targetLocator->expects($this->once())->method('window')->with('testing'); + $this->driver->expects($this->once())->method('switchTo')->willReturn($targetLocator); + $this->command->run('handle=testing', null, $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['handle=', false], + ['handle=ABC-123', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Command/Window/SetWindowSizeCommandTest.php b/tests/Command/Window/SetWindowSizeCommandTest.php new file mode 100644 index 00000000..7e109a02 --- /dev/null +++ b/tests/Command/Window/SetWindowSizeCommandTest.php @@ -0,0 +1,65 @@ +createMock(WebDriverWindow::class); + $window->expects($this->once())->method('setSize')->with($this->callback(function ($dimention) { + return $dimention instanceof WebDriverDimension + && 1280 === $dimention->getWidth() + && 800 === $dimention->getHeight(); + })); + $options = $this->createMock(WebDriverOptions::class); + $options->expects($this->once())->method('window')->willReturn($window); + $this->driver->expects($this->once())->method('manage')->willReturn($options); + $this->command->run('1280x800', null, $this->values, $this->driver); + } + + public function targetProvider(): array + { + return [ + [null, false], + ['', false], + ['anything', false], + ['1+2', false], + ['720p', false], + ['1280x', false], + ['x720', false], + ['1280x720', true], + ]; + } + + public function valueProvider(): array + { + return [ + [null, true], + ['', true], + ['anything', true], + ]; + } +} diff --git a/tests/Entity/Model/RevisionTest.php b/tests/Entity/Model/RevisionTest.php index 75dbfeb7..6a7f116d 100644 --- a/tests/Entity/Model/RevisionTest.php +++ b/tests/Entity/Model/RevisionTest.php @@ -15,16 +15,10 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision * @uses \Tienvx\Bundle\MbtBundle\Entity\Model * @uses \Tienvx\Bundle\MbtBundle\Model\Model - * @uses \Tienvx\Bundle\MbtBundle\Command\CommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\CommandRunnerManager - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\AlertCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\AssertionRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\KeyboardCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\MouseCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\ScriptCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\StoreCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\WaitCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\WindowCommandRunner + * @uses \Tienvx\Bundle\MbtBundle\Command\CommandManager + * @uses \Tienvx\Bundle\MbtBundle\Command\AbstractCommand + * @uses \Tienvx\Bundle\MbtBundle\Command\Mouse\ClickAtCommand + * @uses \Tienvx\Bundle\MbtBundle\Command\Mouse\ClickCommand * @uses \Tienvx\Bundle\MbtBundle\Validator\ValidCommandValidator * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision\Command * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision\Place diff --git a/tests/Factory/Model/Revision/CommandFactoryTest.php b/tests/Factory/Model/Revision/CommandFactoryTest.php index 087a8c89..58e256fb 100644 --- a/tests/Factory/Model/Revision/CommandFactoryTest.php +++ b/tests/Factory/Model/Revision/CommandFactoryTest.php @@ -3,7 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Factory\Model\Revision; use PHPUnit\Framework\TestCase; -use Tienvx\Bundle\MbtBundle\Command\Runner\StoreCommandRunner; use Tienvx\Bundle\MbtBundle\Factory\Model\Revision\CommandFactory; /** @@ -18,7 +17,7 @@ class CommandFactoryTest extends TestCase protected function setUp(): void { $this->data = [ - 'command' => StoreCommandRunner::STORE, + 'command' => 'store', 'target' => '123', 'value' => 'var', ]; diff --git a/tests/Fixtures/Validator/CustomConstraintValidatorFactory.php b/tests/Fixtures/Validator/CustomConstraintValidatorFactory.php index 7f5ac694..e1f448f4 100644 --- a/tests/Fixtures/Validator/CustomConstraintValidatorFactory.php +++ b/tests/Fixtures/Validator/CustomConstraintValidatorFactory.php @@ -2,19 +2,11 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Fixtures\Validator; +use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidatorFactory; use Symfony\Component\Validator\ConstraintValidatorInterface; -use Tienvx\Bundle\MbtBundle\Command\CommandPreprocessor; -use Tienvx\Bundle\MbtBundle\Command\CommandRunnerManager; -use Tienvx\Bundle\MbtBundle\Command\Runner\AlertCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\AssertionRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\KeyboardCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\MouseCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\ScriptCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\StoreCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\WaitCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\WindowCommandRunner; +use Tienvx\Bundle\MbtBundle\Command\CommandManager; use Tienvx\Bundle\MbtBundle\Validator\ValidCommandValidator; class CustomConstraintValidatorFactory extends ConstraintValidatorFactory @@ -23,19 +15,7 @@ public function getInstance(Constraint $constraint): ConstraintValidatorInterfac { $className = $constraint->validatedBy(); if (ValidCommandValidator::class === $className && !isset($this->validators[$className])) { - $this->validators[$className] = new ValidCommandValidator(new CommandRunnerManager( - [ - new AlertCommandRunner(), - new AssertionRunner(), - new KeyboardCommandRunner(), - new MouseCommandRunner(), - new ScriptCommandRunner(), - new StoreCommandRunner(), - new WaitCommandRunner(), - new WindowCommandRunner(), - ], - new CommandPreprocessor() - )); + $this->validators[$className] = new ValidCommandValidator(new CommandManager(new MockHttpClient())); } return parent::getInstance($constraint); diff --git a/tests/Model/Model/Revision/CommandTest.php b/tests/Model/Model/Revision/CommandTest.php index ce137e59..6c9e8202 100644 --- a/tests/Model/Model/Revision/CommandTest.php +++ b/tests/Model/Model/Revision/CommandTest.php @@ -3,8 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Model\Model\Revision; use PHPUnit\Framework\TestCase; -use Tienvx\Bundle\MbtBundle\Command\Runner\MouseCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\WindowCommandRunner; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\Command; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; @@ -18,7 +16,7 @@ class CommandTest extends TestCase protected function setUp(): void { $this->command = $this->createCommand(); - $this->command->setCommand(WindowCommandRunner::OPEN); + $this->command->setCommand('open'); $this->command->setTarget('http://localhost:1234'); $this->command->setValue('123'); } @@ -36,7 +34,7 @@ public function testUnerialize(): void // phpcs:ignore Generic.Files.LineLength $command = unserialize('O:' . strlen($className) . ':"' . $className . '":3:{s:7:"command";s:5:"click";s:6:"target";s:11:"css=.button";s:5:"value";N;}'); $this->assertInstanceOf(CommandInterface::class, $command); - $this->assertSame(MouseCommandRunner::CLICK, $command->getCommand()); + $this->assertSame('click', $command->getCommand()); $this->assertSame('css=.button', $command->getTarget()); $this->assertSame(null, $command->getValue()); } diff --git a/tests/Model/Model/Revision/PlaceTest.php b/tests/Model/Model/Revision/PlaceTest.php index 0ce2e1fb..86bc4258 100644 --- a/tests/Model/Model/Revision/PlaceTest.php +++ b/tests/Model/Model/Revision/PlaceTest.php @@ -3,7 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Model\Model\Revision; use PHPUnit\Framework\TestCase; -use Tienvx\Bundle\MbtBundle\Command\Runner\AssertionRunner; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\Command; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\Place; @@ -36,10 +35,10 @@ protected function setUpCommands(): void { $this->command1 = new Command(); $this->command2 = new Command(); - $this->command1->setCommand(AssertionRunner::ASSERT_TEXT); + $this->command1->setCommand('assertText'); $this->command1->setTarget('css=.title'); $this->command1->setValue('Hello'); - $this->command2->setCommand(AssertionRunner::ASSERT_ALERT); + $this->command2->setCommand('assertAlert'); $this->command2->setTarget('css=.warning'); $this->command2->setValue('Are you sure?'); } @@ -59,7 +58,7 @@ public function testUnerialize(): void $this->assertInstanceOf(PlaceInterface::class, $place); $this->assertSame('Serialized', $place->getLabel()); $this->assertInstanceOf(CommandInterface::class, $place->getCommands()[0]); - $this->assertSame(AssertionRunner::ASSERT_SELECTED_VALUE, $place->getCommands()[0]->getCommand()); + $this->assertSame('assertSelectedValue', $place->getCommands()[0]->getCommand()); $this->assertSame('css=.country', $place->getCommands()[0]->getTarget()); $this->assertSame('vn', $place->getCommands()[0]->getValue()); } diff --git a/tests/Model/Model/Revision/TransitionTest.php b/tests/Model/Model/Revision/TransitionTest.php index 748ac29a..51a98084 100644 --- a/tests/Model/Model/Revision/TransitionTest.php +++ b/tests/Model/Model/Revision/TransitionTest.php @@ -3,9 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Model\Model\Revision; use PHPUnit\Framework\TestCase; -use Tienvx\Bundle\MbtBundle\Command\Runner\KeyboardCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\MouseCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\StoreCommandRunner; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\Command; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\CommandInterface; use Tienvx\Bundle\MbtBundle\Model\Model\Revision\Transition; @@ -42,10 +39,10 @@ protected function setUpCommands(): void { $this->command1 = new Command(); $this->command2 = new Command(); - $this->command1->setCommand(KeyboardCommandRunner::TYPE); + $this->command1->setCommand('type'); $this->command1->setTarget('css=.email'); $this->command1->setValue('test@example.com'); - $this->command2->setCommand(MouseCommandRunner::CLICK); + $this->command2->setCommand('click'); $this->command2->setTarget('css=.link'); $this->command2->setValue(null); } @@ -76,7 +73,7 @@ public function testUnerialize(): void $this->assertSame([1, 4], $transition->getFromPlaces()); $this->assertSame([15], $transition->getToPlaces()); $this->assertInstanceOf(CommandInterface::class, $transition->getCommands()[0]); - $this->assertSame(StoreCommandRunner::STORE, $transition->getCommands()[0]->getCommand()); + $this->assertSame('store', $transition->getCommands()[0]->getCommand()); $this->assertSame('55', $transition->getCommands()[0]->getTarget()); $this->assertSame('number', $transition->getCommands()[0]->getValue()); $this->assertFalse($transition->isStart()); diff --git a/tests/Service/Step/Runner/StepRunnerTest.php b/tests/Service/Step/Runner/StepRunnerTest.php index e43c3bf1..bb90febe 100644 --- a/tests/Service/Step/Runner/StepRunnerTest.php +++ b/tests/Service/Step/Runner/StepRunnerTest.php @@ -5,10 +5,7 @@ use Facebook\WebDriver\Remote\RemoteWebDriver; use PHPUnit\Framework\TestCase; use SingleColorPetrinet\Model\ColorInterface; -use Tienvx\Bundle\MbtBundle\Command\CommandRunnerManager; -use Tienvx\Bundle\MbtBundle\Command\Runner\AssertionRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\MouseCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\WindowCommandRunner; +use Tienvx\Bundle\MbtBundle\Command\CommandManager; use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; use Tienvx\Bundle\MbtBundle\Factory\Model\Revision\CommandFactory; use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; @@ -33,7 +30,7 @@ class StepRunnerTest extends TestCase { protected RevisionInterface $revision; protected array $commands = []; - protected CommandRunnerManager $commandRunnerManager; + protected CommandManager $commandManager; protected RemoteWebDriver $driver; protected ColorInterface $color; protected array $valuesInstances = []; @@ -47,8 +44,8 @@ protected function setUp(): void $transition = new Transition(), ]; $transition->setCommands([ - $command1 = CommandFactory::create(WindowCommandRunner::OPEN), - $command2 = CommandFactory::create(MouseCommandRunner::CLICK), + $command1 = CommandFactory::create('open'), + $command2 = CommandFactory::create('click'), ]); $this->revision->setTransitions($transitions); $places = [ @@ -56,11 +53,11 @@ protected function setUp(): void $place2 = new Place(), ]; $place1->setCommands([ - $command3 = CommandFactory::create(AssertionRunner::ASSERT_EDITABLE), - $command4 = CommandFactory::create(AssertionRunner::ASSERT_ALERT), + $command3 = CommandFactory::create('assertEditable'), + $command4 = CommandFactory::create('assertAlert'), ]); $place2->setCommands([ - $command5 = CommandFactory::create(AssertionRunner::ASSERT_TEXT), + $command5 = CommandFactory::create('assertText'), ]); $this->revision->setPlaces($places); $assertValuesInstance = function (ValuesInterface $values) { @@ -71,21 +68,54 @@ protected function setUp(): void return true; }; $this->commands = [ - [$command1, $this->callback($assertValuesInstance), $this->driver], - [$command2, $this->callback($assertValuesInstance), $this->driver], - [$command3, $this->callback($assertValuesInstance), $this->driver], - [$command4, $this->callback($assertValuesInstance), $this->driver], - [$command5, $this->callback($assertValuesInstance), $this->driver], + [ + $command1->getCommand(), + $command1->getTarget(), + $command1->getValue(), + $this->callback($assertValuesInstance), + $this->driver, + ], + [ + $command2->getCommand(), + $command2->getTarget(), + $command2->getValue(), + $this->callback($assertValuesInstance), + $this->driver, + ], + [ + $command3->getCommand(), + $command3->getTarget(), + $command3->getValue(), + $this->callback($assertValuesInstance), + $this->driver, + ], + [ + $command4->getCommand(), + $command4->getTarget(), + $command4->getValue(), + $this->callback($assertValuesInstance), + $this->driver, + ], + [ + $command5->getCommand(), + $command5->getTarget(), + $command5->getValue(), + $this->callback($assertValuesInstance), + $this->driver, + ], ]; - $this->commandRunnerManager = $this->createMock(CommandRunnerManager::class); + $this->commandManager = $this->createMock(CommandManager::class); } public function testRun(): void { $step = new Step([0 => 1, 1 => 1], $this->color, 0); - $this->commandRunnerManager->expects($this->exactly(5))->method('run')->withConsecutive(...$this->commands); - $stepRunner = new StepRunner($this->commandRunnerManager); + $this->commandManager + ->expects($this->exactly(5)) + ->method('run') + ->withConsecutive(...$this->commands); + $stepRunner = new StepRunner($this->commandManager); $stepRunner->run($step, $this->revision, $this->driver); $this->assertCount(3, $this->valuesInstances); } diff --git a/tests/Validator/ValidCommandValidatorTest.php b/tests/Validator/ValidCommandValidatorTest.php index 7e2fd529..737d511c 100644 --- a/tests/Validator/ValidCommandValidatorTest.php +++ b/tests/Validator/ValidCommandValidatorTest.php @@ -2,20 +2,13 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Validator; +use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Exception\UnexpectedTypeException; use Symfony\Component\Validator\Exception\UnexpectedValueException; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; -use Tienvx\Bundle\MbtBundle\Command\CommandPreprocessor; -use Tienvx\Bundle\MbtBundle\Command\CommandRunnerManager; -use Tienvx\Bundle\MbtBundle\Command\Runner\AlertCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\AssertionRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\KeyboardCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\MouseCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\ScriptCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\StoreCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\WaitCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\WindowCommandRunner; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Tienvx\Bundle\MbtBundle\Command\CommandManager; use Tienvx\Bundle\MbtBundle\Validator\ValidCommand; use Tienvx\Bundle\MbtBundle\Validator\ValidCommandValidator; use Tienvx\Bundle\MbtBundle\ValueObject\Model\Command; @@ -23,35 +16,29 @@ /** * @covers \Tienvx\Bundle\MbtBundle\Validator\ValidCommandValidator * - * @uses \Tienvx\Bundle\MbtBundle\Command\CommandRunnerManager - * @uses \Tienvx\Bundle\MbtBundle\Command\CommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\AlertCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\AssertionRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\KeyboardCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\MouseCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\ScriptCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\StoreCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\WaitCommandRunner - * @uses \Tienvx\Bundle\MbtBundle\Command\Runner\WindowCommandRunner + * @uses \Tienvx\Bundle\MbtBundle\Command\CommandManager + * @uses \Tienvx\Bundle\MbtBundle\Command\AbstractCommand + * @uses \Tienvx\Bundle\MbtBundle\Command\Mouse\AbstractMousePointCommand + * @uses \Tienvx\Bundle\MbtBundle\Command\Mouse\ClickAtCommand + * @uses \Tienvx\Bundle\MbtBundle\Command\Mouse\ClickCommand + * @uses \Tienvx\Bundle\MbtBundle\Command\Window\AbstractWindowCommand + * @uses \Tienvx\Bundle\MbtBundle\Command\Window\OpenCommand + * @uses \Tienvx\Bundle\MbtBundle\Command\Mouse\DragAndDropToObjectCommand * @uses \Tienvx\Bundle\MbtBundle\Model\Model\Revision\Command */ class ValidCommandValidatorTest extends ConstraintValidatorTestCase { + protected HttpClientInterface|MockObject $httpClient; + + protected function setUp(): void + { + $this->httpClient = $this->createMock(HttpClientInterface::class); + parent::setUp(); + } + protected function createValidator() { - return new ValidCommandValidator(new CommandRunnerManager( - [ - new AlertCommandRunner(), - new AssertionRunner(), - new KeyboardCommandRunner(), - new MouseCommandRunner(), - new ScriptCommandRunner(), - new StoreCommandRunner(), - new WaitCommandRunner(), - new WindowCommandRunner(), - ], - new CommandPreprocessor() - )); + return new ValidCommandValidator(new CommandManager($this->httpClient)); } /** @@ -67,7 +54,7 @@ public function testValidCommand($command) public function validCommandsProvider(): array { $command = new Command(); - $command->setCommand(MouseCommandRunner::CLICK_AT); + $command->setCommand('clickAt'); $command->setTarget('css=.button'); $command->setValue('123,234'); @@ -80,7 +67,7 @@ public function validCommandsProvider(): array public function testInvalidCommand() { $constraint = new ValidCommand([ - 'commandMessage' => 'invalid command', + 'invalidCommandMessage' => 'invalid command', ]); $command = new Command(); @@ -101,7 +88,7 @@ public function testRequiredTarget() ]); $command = new Command(); - $command->setCommand(MouseCommandRunner::CLICK); + $command->setCommand('click'); $command->setTarget(null); $this->validator->validate($command, $constraint); @@ -118,7 +105,7 @@ public function testRequiredValue() ]); $command = new Command(); - $command->setCommand(MouseCommandRunner::CLICK_AT); + $command->setCommand('clickAt'); $command->setTarget('css=.button'); $command->setValue(null); $this->validator->validate($command, $constraint); @@ -136,7 +123,7 @@ public function testInvalidTarget() ]); $command = new Command(); - $command->setCommand(WindowCommandRunner::OPEN); + $command->setCommand('open'); $command->setTarget('testing'); $this->validator->validate($command, $constraint); @@ -146,6 +133,24 @@ public function testInvalidTarget() ->assertRaised(); } + public function testInvalidValue() + { + $constraint = new ValidCommand([ + 'valueInvalidMessage' => 'invalid value', + ]); + + $command = new Command(); + $command->setCommand('dragAndDropToObject'); + $command->setTarget('css=.from'); + $command->setValue('.to'); + $this->validator->validate($command, $constraint); + + $this->buildViolation('invalid value') + ->setCode(ValidCommand::IS_COMMAND_INVALID_ERROR) + ->atPath('property.path.value') + ->assertRaised(); + } + public function testUnexpectedType(): void { $this->expectException(UnexpectedTypeException::class); diff --git a/translations/validators.en.php b/translations/validators.en.php index 8fd8b837..45c0a9a3 100644 --- a/translations/validators.en.php +++ b/translations/validators.en.php @@ -13,6 +13,7 @@ 'required_target' => 'The target is required.', 'invalid_target' => 'The target is not valid.', 'required_value' => 'The value is required.', + 'invalid_value' => 'The value is not valid.', ], ], 'bug' => [ From ffe2ad5642151cad967aaaf266e884e972264966 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 24 Jul 2022 21:06:06 +0700 Subject: [PATCH 78/85] CS for config --- .github/workflows/main.yml | 4 ++-- .php-cs-fixer.php | 1 + config/services.php | 10 ---------- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0236f913..77584fda 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,13 +27,13 @@ jobs: dependency-versions: ${{ matrix.dependency-versions }} - name: Run PHP CS - run: phpcs --standard=PSR12 src tests + run: phpcs --standard=PSR12 src tests config - name: Run PHP CS Fixer run: php-cs-fixer fix --diff --dry-run - name: Run PHPStan - run: phpstan analyse src tests + run: phpstan analyse src tests config - name: Test & Generate Code Coverage run: ./vendor/bin/phpunit diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 00759740..5ec2e2b1 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -3,6 +3,7 @@ $finder = PhpCsFixer\Finder::create() ->in(__DIR__.'/src') ->in(__DIR__.'/tests') + ->in(__DIR__.'/config') ; $config = new PhpCsFixer\Config(); diff --git a/config/services.php b/config/services.php index 0494fffb..dc7d100f 100644 --- a/config/services.php +++ b/config/services.php @@ -13,18 +13,8 @@ use Tienvx\AssignmentsEvaluator\AssignmentsEvaluator; use Tienvx\Bundle\MbtBundle\Channel\ChannelManager; use Tienvx\Bundle\MbtBundle\Channel\ChannelManagerInterface; -use Tienvx\Bundle\MbtBundle\Command\CommandRunnerInterface; use Tienvx\Bundle\MbtBundle\Command\CommandManager; use Tienvx\Bundle\MbtBundle\Command\CommandManagerInterface; -use Tienvx\Bundle\MbtBundle\Command\Runner\AlertCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\AssertionRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\CustomCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\KeyboardCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\MouseCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\ScriptCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\StoreCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\WaitCommandRunner; -use Tienvx\Bundle\MbtBundle\Command\Runner\WindowCommandRunner; use Tienvx\Bundle\MbtBundle\EventListener\EntitySubscriber; use Tienvx\Bundle\MbtBundle\Generator\GeneratorManager; use Tienvx\Bundle\MbtBundle\Generator\GeneratorManagerInterface; From 2f87635d97c0bc731eda5a9c13fa49612fcbb4d6 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 24 Jul 2022 21:46:00 +0700 Subject: [PATCH 79/85] Fix service not found --- src/TienvxMbtBundle.php | 8 +++---- tests/TienvxMbtBundleTest.php | 41 ++++++++++++++++------------------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/TienvxMbtBundle.php b/src/TienvxMbtBundle.php index 04b9dfaf..14d2ceae 100644 --- a/src/TienvxMbtBundle.php +++ b/src/TienvxMbtBundle.php @@ -6,9 +6,9 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\HttpKernel\Bundle\AbstractBundle; -use Tienvx\Bundle\MbtBundle\Command\CommandManagerInterface; +use Tienvx\Bundle\MbtBundle\Command\CommandManager; use Tienvx\Bundle\MbtBundle\DependencyInjection\Compiler\PluginPass; -use Tienvx\Bundle\MbtBundle\Service\SelenoidHelperInterface; +use Tienvx\Bundle\MbtBundle\Service\SelenoidHelper; class TienvxMbtBundle extends AbstractBundle { @@ -30,11 +30,11 @@ public function loadExtension(array $config, ContainerConfigurator $container, C $container->import('../config/services.php'); $container->services() - ->get(SelenoidHelperInterface::class) + ->get(SelenoidHelper::class) ->call('setWebdriverUri', [$config[static::WEBDRIVER_URI]]) ; $container->services() - ->get(CommandManagerInterface::class) + ->get(CommandManager::class) ->call('setUploadDir', [$config[static::UPLOAD_DIR]]) ; } diff --git a/tests/TienvxMbtBundleTest.php b/tests/TienvxMbtBundleTest.php index 500c4518..52049106 100644 --- a/tests/TienvxMbtBundleTest.php +++ b/tests/TienvxMbtBundleTest.php @@ -5,11 +5,13 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; +use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; -// use Symfony\Component\DependencyInjection\Loader\Configurator\ServiceConfigurator; -// use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Tienvx\Bundle\MbtBundle\Command\CommandManager; use Tienvx\Bundle\MbtBundle\DependencyInjection\Compiler\PluginPass; +use Tienvx\Bundle\MbtBundle\Service\SelenoidHelper; use Tienvx\Bundle\MbtBundle\TienvxMbtBundle; /** @@ -23,14 +25,12 @@ class TienvxMbtBundleTest extends TestCase ]; protected ContainerBuilder $builder; - // protected ContainerConfigurator|MockObject $container; protected DefinitionConfigurator|MockObject $definition; protected TienvxMbtBundle $bundle; protected function setUp(): void { $this->builder = new ContainerBuilder(); - // $this->container = $this->createMock(ContainerConfigurator::class); $this->definition = $this->createMock(DefinitionConfigurator::class); $this->bundle = new TienvxMbtBundle(); } @@ -56,25 +56,22 @@ public function testConfigure(): void $this->bundle->configure($this->definition); } - /*public function testLoadExtension(): void + public function testLoadExtension(): void { - $this->container->expects($this->once())->method('import')->with('../config/services.php'); - $services = $this->createMock(ServicesConfigurator::class); - $selenoidHelper = $this->createMock(ServiceConfigurator::class); - $customCommandRunner = $this->createMock(ServiceConfigurator::class); - $services->expects($this->exactly(2))->method('get')->willReturnMap([ - [SelenoidHelperInterface::class, $selenoidHelper], - [CustomCommandRunner::class, $customCommandRunner], - ]); - $selenoidHelper - ->expects($this->once()) - ->method('call') - ->with('setWebdriverUri', [static::CONFIG[TienvxMbtBundle::WEBDRIVER_URI]]); - $customCommandRunner->expects($this->exactly(2))->method('call')->withConsecutive([ + $instanceof = []; + $container = new ContainerConfigurator( + $this->builder, + new PhpFileLoader($this->builder, new FileLocator(\dirname(__DIR__) . '/config')), + $instanceof, + '', + '' + ); + $this->bundle->loadExtension(static::CONFIG, $container, $this->builder); + $this->assertSame([ ['setWebdriverUri', [static::CONFIG[TienvxMbtBundle::WEBDRIVER_URI]]], + ], $this->builder->findDefinition(SelenoidHelper::class)->getMethodCalls()); + $this->assertSame([ ['setUploadDir', [static::CONFIG[TienvxMbtBundle::UPLOAD_DIR]]], - ]); - $this->container->expects($this->exactly(2))->method('services')->willReturn($services); - $this->bundle->loadExtension(static::CONFIG, $this->container, $this->builder); - }*/ + ], $this->builder->findDefinition(CommandManager::class)->getMethodCalls()); + } } From 386a1825fc81dd1e0a9273d5eb107d9a312fd44d Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 25 Jul 2022 23:13:12 +0700 Subject: [PATCH 80/85] Update commands in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 378eb17a..c09a76f5 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,9 @@ vendor/bin/phpunit ## Validate code with coding standards ```shell -phpcs --standard=PSR12 src tests +phpcs --standard=PSR12 src tests config php-cs-fixer fix --diff --dry-run -phpstan analyse src tests +phpstan analyse src tests config ``` ## Built With From 99fcef1f830bc9dd66b5f80b491f49a3d3260470 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 26 Jul 2022 19:12:59 +0700 Subject: [PATCH 81/85] Promote properties in constructors --- src/EventListener/EntitySubscriber.php | 5 +--- src/Generator/RandomGenerator.php | 17 +++---------- src/Message/RecordVideoMessage.php | 5 +--- src/Message/ReduceBugMessage.php | 5 +--- src/Message/ReduceStepsMessage.php | 17 +++++-------- src/Message/ReportBugMessage.php | 5 +--- src/Message/RunTaskMessage.php | 5 +--- .../RecordVideoMessageHandler.php | 5 +--- .../ReduceBugMessageHandler.php | 5 +--- .../ReduceStepsMessageHandler.php | 5 +--- .../ReportBugMessageHandler.php | 5 +--- src/MessageHandler/RunTaskMessageHandler.php | 5 +--- src/Model/Bug/Step.php | 13 ++++------ src/Model/Values.php | 4 +-- src/Plugin/PluginManager.php | 11 +++----- src/Reducer/DispatcherTemplate.php | 5 +--- src/Reducer/HandlerTemplate.php | 19 +++++--------- src/Service/Bug/BugHelper.php | 25 +++++-------------- src/Service/Petrinet/MarkingHelper.php | 5 +--- src/Service/Petrinet/PetrinetHelper.php | 13 +++------- .../Step/Builder/PetrinetDomainLogic.php | 13 +++------- .../Step/Builder/ShortestPathStepsBuilder.php | 13 +++------- .../Step/Runner/ExploreStepsRunner.php | 4 +-- src/Service/Step/Runner/StepRunner.php | 5 +--- src/Service/Step/Runner/StepsRunner.php | 11 +++----- src/Service/Task/TaskHelper.php | 17 +++---------- 26 files changed, 63 insertions(+), 179 deletions(-) diff --git a/src/EventListener/EntitySubscriber.php b/src/EventListener/EntitySubscriber.php index 0b6ea242..aba160f5 100644 --- a/src/EventListener/EntitySubscriber.php +++ b/src/EventListener/EntitySubscriber.php @@ -11,13 +11,10 @@ class EntitySubscriber implements EventSubscriber { - protected MessageBusInterface $messageBus; - protected string $reducer; - public function __construct(MessageBusInterface $messageBus) + public function __construct(protected MessageBusInterface $messageBus) { - $this->messageBus = $messageBus; } public function postPersist(LifecycleEventArgs $args): void diff --git a/src/Generator/RandomGenerator.php b/src/Generator/RandomGenerator.php index 94269c4f..49534fd9 100644 --- a/src/Generator/RandomGenerator.php +++ b/src/Generator/RandomGenerator.php @@ -20,21 +20,12 @@ class RandomGenerator extends AbstractGenerator public const MAX_TRANSITION_COVERAGE = 100; public const MAX_PLACE_COVERAGE = 100; - protected PetrinetHelperInterface $petrinetHelper; - protected MarkingHelperInterface $markingHelper; - protected ModelHelperInterface $modelHelper; - protected GuardedTransitionServiceInterface $transitionService; - public function __construct( - PetrinetHelperInterface $petrinetHelper, - MarkingHelperInterface $markingHelper, - ModelHelperInterface $modelHelper, - GuardedTransitionServiceInterface $transitionService + protected PetrinetHelperInterface $petrinetHelper, + protected MarkingHelperInterface $markingHelper, + protected ModelHelperInterface $modelHelper, + protected GuardedTransitionServiceInterface $transitionService ) { - $this->petrinetHelper = $petrinetHelper; - $this->markingHelper = $markingHelper; - $this->modelHelper = $modelHelper; - $this->transitionService = $transitionService; } public static function getName(): string diff --git a/src/Message/RecordVideoMessage.php b/src/Message/RecordVideoMessage.php index 9316b80f..11b10275 100644 --- a/src/Message/RecordVideoMessage.php +++ b/src/Message/RecordVideoMessage.php @@ -4,11 +4,8 @@ class RecordVideoMessage implements MessageInterface { - protected int $bugId; - - public function __construct(int $bugId) + public function __construct(protected int $bugId) { - $this->bugId = $bugId; } public function getBugId(): int diff --git a/src/Message/ReduceBugMessage.php b/src/Message/ReduceBugMessage.php index d802a850..a9253eb9 100644 --- a/src/Message/ReduceBugMessage.php +++ b/src/Message/ReduceBugMessage.php @@ -4,11 +4,8 @@ class ReduceBugMessage implements MessageInterface { - protected int $id; - - public function __construct(int $id) + public function __construct(protected int $id) { - $this->id = $id; } public function getId(): int diff --git a/src/Message/ReduceStepsMessage.php b/src/Message/ReduceStepsMessage.php index 110051b2..12f8e636 100644 --- a/src/Message/ReduceStepsMessage.php +++ b/src/Message/ReduceStepsMessage.php @@ -4,17 +4,12 @@ class ReduceStepsMessage implements MessageInterface { - protected int $bugId; - protected int $length; - protected int $from; - protected int $to; - - public function __construct(int $bugId, int $length, int $from, int $to) - { - $this->bugId = $bugId; - $this->length = $length; - $this->from = $from; - $this->to = $to; + public function __construct( + protected int $bugId, + protected int $length, + protected int $from, + protected int $to + ) { } public function getBugId(): int diff --git a/src/Message/ReportBugMessage.php b/src/Message/ReportBugMessage.php index 05795e8c..090e915f 100644 --- a/src/Message/ReportBugMessage.php +++ b/src/Message/ReportBugMessage.php @@ -4,11 +4,8 @@ class ReportBugMessage implements MessageInterface { - protected int $bugId; - - public function __construct(int $bugId) + public function __construct(protected int $bugId) { - $this->bugId = $bugId; } public function getBugId(): int diff --git a/src/Message/RunTaskMessage.php b/src/Message/RunTaskMessage.php index 2110d432..dd7878b5 100644 --- a/src/Message/RunTaskMessage.php +++ b/src/Message/RunTaskMessage.php @@ -4,11 +4,8 @@ class RunTaskMessage implements MessageInterface { - protected int $id; - - public function __construct(int $id) + public function __construct(protected int $id) { - $this->id = $id; } public function getId(): int diff --git a/src/MessageHandler/RecordVideoMessageHandler.php b/src/MessageHandler/RecordVideoMessageHandler.php index 36deb367..b360b651 100644 --- a/src/MessageHandler/RecordVideoMessageHandler.php +++ b/src/MessageHandler/RecordVideoMessageHandler.php @@ -8,11 +8,8 @@ class RecordVideoMessageHandler implements MessageHandlerInterface { - protected BugHelperInterface $bugHelper; - - public function __construct(BugHelperInterface $bugHelper) + public function __construct(protected BugHelperInterface $bugHelper) { - $this->bugHelper = $bugHelper; } public function __invoke(RecordVideoMessage $message): void diff --git a/src/MessageHandler/ReduceBugMessageHandler.php b/src/MessageHandler/ReduceBugMessageHandler.php index 937469d4..42010be1 100644 --- a/src/MessageHandler/ReduceBugMessageHandler.php +++ b/src/MessageHandler/ReduceBugMessageHandler.php @@ -8,11 +8,8 @@ class ReduceBugMessageHandler implements MessageHandlerInterface { - protected BugHelperInterface $bugHelper; - - public function __construct(BugHelperInterface $bugHelper) + public function __construct(protected BugHelperInterface $bugHelper) { - $this->bugHelper = $bugHelper; } public function __invoke(ReduceBugMessage $message): void diff --git a/src/MessageHandler/ReduceStepsMessageHandler.php b/src/MessageHandler/ReduceStepsMessageHandler.php index 61cfd587..a6ffd4d3 100644 --- a/src/MessageHandler/ReduceStepsMessageHandler.php +++ b/src/MessageHandler/ReduceStepsMessageHandler.php @@ -8,11 +8,8 @@ class ReduceStepsMessageHandler implements MessageHandlerInterface { - protected BugHelperInterface $bugHelper; - - public function __construct(BugHelperInterface $bugHelper) + public function __construct(protected BugHelperInterface $bugHelper) { - $this->bugHelper = $bugHelper; } public function __invoke(ReduceStepsMessage $message): void diff --git a/src/MessageHandler/ReportBugMessageHandler.php b/src/MessageHandler/ReportBugMessageHandler.php index 9bd67b9a..28047960 100644 --- a/src/MessageHandler/ReportBugMessageHandler.php +++ b/src/MessageHandler/ReportBugMessageHandler.php @@ -8,11 +8,8 @@ class ReportBugMessageHandler implements MessageHandlerInterface { - protected BugHelperInterface $bugHelper; - - public function __construct(BugHelperInterface $bugHelper) + public function __construct(protected BugHelperInterface $bugHelper) { - $this->bugHelper = $bugHelper; } public function __invoke(ReportBugMessage $message): void diff --git a/src/MessageHandler/RunTaskMessageHandler.php b/src/MessageHandler/RunTaskMessageHandler.php index f35b2102..59668e6e 100644 --- a/src/MessageHandler/RunTaskMessageHandler.php +++ b/src/MessageHandler/RunTaskMessageHandler.php @@ -8,11 +8,8 @@ class RunTaskMessageHandler implements MessageHandlerInterface { - protected TaskHelperInterface $taskHelper; - - public function __construct(TaskHelperInterface $taskHelper) + public function __construct(protected TaskHelperInterface $taskHelper) { - $this->taskHelper = $taskHelper; } public function __invoke(RunTaskMessage $message): void diff --git a/src/Model/Bug/Step.php b/src/Model/Bug/Step.php index 4322cb29..d966da01 100644 --- a/src/Model/Bug/Step.php +++ b/src/Model/Bug/Step.php @@ -8,10 +8,6 @@ class Step implements StepInterface, NodeIdentifierInterface { - protected ColorInterface $color; - protected array $places; - protected int $transition; - public function __serialize(): array { return [ @@ -28,11 +24,12 @@ public function __unserialize(array $data) $this->transition = $data['transition']; } - public function __construct(array $places, ColorInterface $color, int $transition) - { + public function __construct( + protected array $places, + protected ColorInterface $color, + protected int $transition + ) { $this->setPlaces($places); - $this->setColor($color); - $this->setTransition($transition); } public function __clone() diff --git a/src/Model/Values.php b/src/Model/Values.php index 0d689c59..59d5460e 100644 --- a/src/Model/Values.php +++ b/src/Model/Values.php @@ -4,9 +4,7 @@ class Values implements ValuesInterface { - protected array $values = []; - - public function __construct(array $values = []) + public function __construct(protected array $values = []) { $this->values = []; diff --git a/src/Plugin/PluginManager.php b/src/Plugin/PluginManager.php index b0a124a7..87ba558c 100644 --- a/src/Plugin/PluginManager.php +++ b/src/Plugin/PluginManager.php @@ -7,13 +7,10 @@ class PluginManager implements PluginManagerInterface { - protected ServiceLocator $locator; - protected array $plugins; - - public function __construct(ServiceLocator $locator, array $plugins) - { - $this->locator = $locator; - $this->plugins = $plugins; + public function __construct( + protected ServiceLocator $locator, + protected array $plugins + ) { } public function all(): array diff --git a/src/Reducer/DispatcherTemplate.php b/src/Reducer/DispatcherTemplate.php index 39e43460..593286b0 100644 --- a/src/Reducer/DispatcherTemplate.php +++ b/src/Reducer/DispatcherTemplate.php @@ -10,11 +10,8 @@ abstract class DispatcherTemplate implements DispatcherInterface { protected const MIN_STEPS = 3; - protected MessageBusInterface $messageBus; - - public function __construct(MessageBusInterface $messageBus) + public function __construct(protected MessageBusInterface $messageBus) { - $this->messageBus = $messageBus; } public function dispatch(BugInterface $bug): int diff --git a/src/Reducer/HandlerTemplate.php b/src/Reducer/HandlerTemplate.php index dc0a5df9..74a8aa16 100644 --- a/src/Reducer/HandlerTemplate.php +++ b/src/Reducer/HandlerTemplate.php @@ -8,26 +8,19 @@ use Tienvx\Bundle\MbtBundle\Message\ReduceBugMessage; use Tienvx\Bundle\MbtBundle\Model\BugInterface; use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; +use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Builder\StepsBuilderInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; abstract class HandlerTemplate implements HandlerInterface { - protected BugRepositoryInterface $bugRepository; - protected MessageBusInterface $messageBus; - protected BugStepsRunner $stepsRunner; - protected StepsBuilderInterface $stepsBuilder; - public function __construct( - BugRepositoryInterface $bugRepository, - MessageBusInterface $messageBus, - BugStepsRunner $stepsRunner, - StepsBuilderInterface $stepsBuilder + protected BugRepositoryInterface $bugRepository, + protected MessageBusInterface $messageBus, + protected BugStepsRunner $stepsRunner, + protected StepsBuilderInterface $stepsBuilder, + protected ConfigInterface $config ) { - $this->bugRepository = $bugRepository; - $this->messageBus = $messageBus; - $this->stepsRunner = $stepsRunner; - $this->stepsBuilder = $stepsBuilder; } public function handle(BugInterface $bug, int $from, int $to): void diff --git a/src/Service/Bug/BugHelper.php b/src/Service/Bug/BugHelper.php index cbb4f976..c5e8e470 100644 --- a/src/Service/Bug/BugHelper.php +++ b/src/Service/Bug/BugHelper.php @@ -17,27 +17,14 @@ class BugHelper implements BugHelperInterface { - protected ReducerManagerInterface $reducerManager; - protected BugRepositoryInterface $bugRepository; - protected MessageBusInterface $messageBus; - protected BugNotifierInterface $bugNotifier; - protected BugStepsRunner $stepsRunner; - protected ConfigInterface $config; - public function __construct( - ReducerManagerInterface $reducerManager, - BugRepositoryInterface $bugRepository, - MessageBusInterface $messageBus, - BugNotifierInterface $bugNotifier, - BugStepsRunner $stepsRunner, - ConfigInterface $config + protected ReducerManagerInterface $reducerManager, + protected BugRepositoryInterface $bugRepository, + protected MessageBusInterface $messageBus, + protected BugNotifierInterface $bugNotifier, + protected BugStepsRunner $stepsRunner, + protected ConfigInterface $config ) { - $this->reducerManager = $reducerManager; - $this->bugRepository = $bugRepository; - $this->messageBus = $messageBus; - $this->bugNotifier = $bugNotifier; - $this->stepsRunner = $stepsRunner; - $this->config = $config; } public function reduceBug(int $bugId): void diff --git a/src/Service/Petrinet/MarkingHelper.php b/src/Service/Petrinet/MarkingHelper.php index 2a5597c6..ba732867 100644 --- a/src/Service/Petrinet/MarkingHelper.php +++ b/src/Service/Petrinet/MarkingHelper.php @@ -13,11 +13,8 @@ class MarkingHelper implements MarkingHelperInterface { - protected ColorfulFactoryInterface $colorfulFactory; - - public function __construct(ColorfulFactoryInterface $colorfulFactory) + public function __construct(protected ColorfulFactoryInterface $colorfulFactory) { - $this->colorfulFactory = $colorfulFactory; } /** diff --git a/src/Service/Petrinet/PetrinetHelper.php b/src/Service/Petrinet/PetrinetHelper.php index 45907ec5..dc9ea438 100644 --- a/src/Service/Petrinet/PetrinetHelper.php +++ b/src/Service/Petrinet/PetrinetHelper.php @@ -18,18 +18,11 @@ class PetrinetHelper implements PetrinetHelperInterface { public const FAKE_START_PLACE_ID = -1; - protected ColorfulFactoryInterface $colorfulFactory; - protected ExpressionLanguage $expressionLanguage; - protected AssignmentsEvaluator $assignmentsEvaluator; - public function __construct( - ColorfulFactoryInterface $colorfulFactory, - ExpressionLanguage $expressionLanguage, - AssignmentsEvaluator $assignmentsEvaluator + protected ColorfulFactoryInterface $colorfulFactory, + protected ExpressionLanguage $expressionLanguage, + protected AssignmentsEvaluator $assignmentsEvaluator ) { - $this->colorfulFactory = $colorfulFactory; - $this->expressionLanguage = $expressionLanguage; - $this->assignmentsEvaluator = $assignmentsEvaluator; } public function build(RevisionInterface $revision): PetrinetInterface diff --git a/src/Service/Step/Builder/PetrinetDomainLogic.php b/src/Service/Step/Builder/PetrinetDomainLogic.php index 9a1de37a..6d3ec638 100644 --- a/src/Service/Step/Builder/PetrinetDomainLogic.php +++ b/src/Service/Step/Builder/PetrinetDomainLogic.php @@ -12,18 +12,11 @@ class PetrinetDomainLogic implements DomainLogicInterface { - protected GuardedTransitionServiceInterface $transitionService; - protected MarkingHelperInterface $markingHelper; - protected PetrinetInterface $petrinet; - public function __construct( - GuardedTransitionServiceInterface $transitionService, - MarkingHelperInterface $markingHelper, - PetrinetInterface $petrinet + protected GuardedTransitionServiceInterface $transitionService, + protected MarkingHelperInterface $markingHelper, + protected PetrinetInterface $petrinet ) { - $this->transitionService = $transitionService; - $this->markingHelper = $markingHelper; - $this->petrinet = $petrinet; } public function calculateEstimatedCost(mixed $fromNode, mixed $toNode): float|int diff --git a/src/Service/Step/Builder/ShortestPathStepsBuilder.php b/src/Service/Step/Builder/ShortestPathStepsBuilder.php index 5cdd29b8..d3d4419d 100644 --- a/src/Service/Step/Builder/ShortestPathStepsBuilder.php +++ b/src/Service/Step/Builder/ShortestPathStepsBuilder.php @@ -17,18 +17,11 @@ class ShortestPathStepsBuilder implements StepsBuilderInterface { - protected PetrinetHelperInterface $petrinetHelper; - protected GuardedTransitionServiceInterface $transitionService; - protected MarkingHelperInterface $markingHelper; - public function __construct( - PetrinetHelperInterface $petrinetHelper, - GuardedTransitionServiceInterface $transitionService, - MarkingHelperInterface $markingHelper + protected PetrinetHelperInterface $petrinetHelper, + protected GuardedTransitionServiceInterface $transitionService, + protected MarkingHelperInterface $markingHelper ) { - $this->petrinetHelper = $petrinetHelper; - $this->transitionService = $transitionService; - $this->markingHelper = $markingHelper; } /** diff --git a/src/Service/Step/Runner/ExploreStepsRunner.php b/src/Service/Step/Runner/ExploreStepsRunner.php index b4dfc22f..1845c362 100644 --- a/src/Service/Step/Runner/ExploreStepsRunner.php +++ b/src/Service/Step/Runner/ExploreStepsRunner.php @@ -15,15 +15,13 @@ class ExploreStepsRunner extends StepsRunner { protected array $steps; - protected ConfigInterface $config; public function __construct( SelenoidHelperInterface $selenoidHelper, StepRunnerInterface $stepRunner, - ConfigInterface $config + protected ConfigInterface $config ) { parent::__construct($selenoidHelper, $stepRunner); - $this->config = $config; } protected function start(DebugInterface $entity): RemoteWebDriver diff --git a/src/Service/Step/Runner/StepRunner.php b/src/Service/Step/Runner/StepRunner.php index 73c5c4c5..384ac1b2 100644 --- a/src/Service/Step/Runner/StepRunner.php +++ b/src/Service/Step/Runner/StepRunner.php @@ -13,11 +13,8 @@ class StepRunner implements StepRunnerInterface { - protected CommandManagerInterface $commandManager; - - public function __construct(CommandManagerInterface $commandManager) + public function __construct(protected CommandManagerInterface $commandManager) { - $this->commandManager = $commandManager; } public function run(StepInterface $step, RevisionInterface $revision, RemoteWebDriver $driver): void diff --git a/src/Service/Step/Runner/StepsRunner.php b/src/Service/Step/Runner/StepsRunner.php index c88d5d34..b539c677 100644 --- a/src/Service/Step/Runner/StepsRunner.php +++ b/src/Service/Step/Runner/StepsRunner.php @@ -13,13 +13,10 @@ abstract class StepsRunner implements StepsRunnerInterface { - protected SelenoidHelperInterface $selenoidHelper; - protected StepRunnerInterface $stepRunner; - - public function __construct(SelenoidHelperInterface $selenoidHelper, StepRunnerInterface $stepRunner) - { - $this->selenoidHelper = $selenoidHelper; - $this->stepRunner = $stepRunner; + public function __construct( + protected SelenoidHelperInterface $selenoidHelper, + protected StepRunnerInterface $stepRunner + ) { } /** diff --git a/src/Service/Task/TaskHelper.php b/src/Service/Task/TaskHelper.php index 55a65cf3..b449b278 100644 --- a/src/Service/Task/TaskHelper.php +++ b/src/Service/Task/TaskHelper.php @@ -13,21 +13,12 @@ class TaskHelper implements TaskHelperInterface { - protected GeneratorManagerInterface $generatorManager; - protected TaskRepositoryInterface $taskRepository; - protected ExploreStepsRunner $stepsRunner; - protected ConfigInterface $config; - public function __construct( - GeneratorManagerInterface $generatorManager, - TaskRepositoryInterface $taskRepository, - ExploreStepsRunner $stepsRunner, - ConfigInterface $config + protected GeneratorManagerInterface $generatorManager, + protected TaskRepositoryInterface $taskRepository, + protected ExploreStepsRunner $stepsRunner, + protected ConfigInterface $config ) { - $this->generatorManager = $generatorManager; - $this->taskRepository = $taskRepository; - $this->stepsRunner = $stepsRunner; - $this->config = $config; } public function run(int $taskId): void From 1c2b8e0359f2114b12170ba75ac9b13528ac3f34 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 26 Jul 2022 21:32:37 +0700 Subject: [PATCH 82/85] Create new bug while reducing. Allow config default bug title --- src/Message/CreateBugMessage.php | 28 +++++++ .../CreateBugMessageHandler.php | 45 +++++++++++ src/Reducer/HandlerTemplate.php | 7 ++ src/Repository/TaskRepository.php | 7 ++ src/Repository/TaskRepositoryInterface.php | 3 + src/Service/ConfigInterface.php | 4 + .../Step/Runner/ExploreStepsRunner.php | 14 +--- src/Service/Task/TaskHelper.php | 13 +++- .../CreateBugMessageHandlerTest.php | 77 +++++++++++++++++++ .../RunTaskMessageHandlerTest.php | 3 +- tests/Reducer/HandlerTestCase.php | 51 +++++++++--- tests/Reducer/Random/RandomHandlerTest.php | 4 +- tests/Reducer/Split/SplitHandlerTest.php | 4 +- tests/Repository/TaskRepositoryTest.php | 15 +++- .../Step/Runner/ExploreStepsRunnerTest.php | 16 +--- .../Step/Runner/StepsRunnerTestCase.php | 6 +- tests/Service/Task/TaskHelperTest.php | 50 ++++++++---- 17 files changed, 283 insertions(+), 64 deletions(-) create mode 100644 src/Message/CreateBugMessage.php create mode 100644 src/MessageHandler/CreateBugMessageHandler.php create mode 100644 tests/MessageHandler/CreateBugMessageHandlerTest.php diff --git a/src/Message/CreateBugMessage.php b/src/Message/CreateBugMessage.php new file mode 100644 index 00000000..4d6bf4e7 --- /dev/null +++ b/src/Message/CreateBugMessage.php @@ -0,0 +1,28 @@ +taskId; + } + + public function getSteps(): array + { + return $this->steps; + } + + public function getMessage(): string + { + return $this->message; + } +} diff --git a/src/MessageHandler/CreateBugMessageHandler.php b/src/MessageHandler/CreateBugMessageHandler.php new file mode 100644 index 00000000..bba7da52 --- /dev/null +++ b/src/MessageHandler/CreateBugMessageHandler.php @@ -0,0 +1,45 @@ +taskRepository->find($message->getTaskId()); + + if (!$task instanceof TaskInterface) { + throw new UnexpectedValueException(sprintf( + 'Can not create bug for task %d: task not found', + $message->getTaskId() + )); + } + + $this->taskRepository->addBug($task, $this->createBug($message->getSteps(), $message->getMessage())); + } + + public function createBug(array $steps, string $message): BugInterface + { + $bug = new Bug(); + $bug->setTitle($this->config->getDefaultBugTitle()); + $bug->setSteps($steps); + $bug->setMessage($message); + + return $bug; + } +} diff --git a/src/Reducer/HandlerTemplate.php b/src/Reducer/HandlerTemplate.php index 74a8aa16..4b228b81 100644 --- a/src/Reducer/HandlerTemplate.php +++ b/src/Reducer/HandlerTemplate.php @@ -5,6 +5,7 @@ use Symfony\Component\Messenger\MessageBusInterface; use Throwable; use Tienvx\Bundle\MbtBundle\Exception\StepsNotConnectedException; +use Tienvx\Bundle\MbtBundle\Message\CreateBugMessage; use Tienvx\Bundle\MbtBundle\Message\ReduceBugMessage; use Tienvx\Bundle\MbtBundle\Model\BugInterface; use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; @@ -43,6 +44,12 @@ function (Throwable $throwable) use ($bug, $newSteps): void { if ($throwable->getMessage() === $bug->getMessage()) { $this->bugRepository->updateSteps($bug, $newSteps); $this->messageBus->dispatch(new ReduceBugMessage($bug->getId())); + } elseif ($this->config->shouldCreateNewBugWhileReducing()) { + $this->messageBus->dispatch(new CreateBugMessage( + $bug->getTask()->getId(), + $newSteps, + $throwable->getMessage() + )); } } ); diff --git a/src/Repository/TaskRepository.php b/src/Repository/TaskRepository.php index 3855c268..69ad2a83 100644 --- a/src/Repository/TaskRepository.php +++ b/src/Repository/TaskRepository.php @@ -5,6 +5,7 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; use Tienvx\Bundle\MbtBundle\Entity\Task; +use Tienvx\Bundle\MbtBundle\Model\BugInterface; use Tienvx\Bundle\MbtBundle\Model\TaskInterface; class TaskRepository extends ServiceEntityRepository implements TaskRepositoryInterface @@ -27,4 +28,10 @@ public function stopRunning(TaskInterface $task): void $this->getEntityManager()->getConnection()->connect(); $this->getEntityManager()->flush(); } + + public function addBug(TaskInterface $task, BugInterface $bug): void + { + $task->addBug($bug); + $this->getEntityManager()->flush(); + } } diff --git a/src/Repository/TaskRepositoryInterface.php b/src/Repository/TaskRepositoryInterface.php index 5e33da05..fe51d651 100644 --- a/src/Repository/TaskRepositoryInterface.php +++ b/src/Repository/TaskRepositoryInterface.php @@ -3,6 +3,7 @@ namespace Tienvx\Bundle\MbtBundle\Repository; use Doctrine\Persistence\ObjectRepository; +use Tienvx\Bundle\MbtBundle\Model\BugInterface; use Tienvx\Bundle\MbtBundle\Model\TaskInterface; interface TaskRepositoryInterface extends ObjectRepository @@ -10,4 +11,6 @@ interface TaskRepositoryInterface extends ObjectRepository public function startRunning(TaskInterface $task): void; public function stopRunning(TaskInterface $task): void; + + public function addBug(TaskInterface $task, BugInterface $bug): void; } diff --git a/src/Service/ConfigInterface.php b/src/Service/ConfigInterface.php index cf67b524..69f6cda6 100644 --- a/src/Service/ConfigInterface.php +++ b/src/Service/ConfigInterface.php @@ -11,4 +11,8 @@ public function getReducer(): string; public function shouldReportBug(): bool; public function getMaxSteps(): int; + + public function shouldCreateNewBugWhileReducing(): bool; + + public function getDefaultBugTitle(): string; } diff --git a/src/Service/Step/Runner/ExploreStepsRunner.php b/src/Service/Step/Runner/ExploreStepsRunner.php index 1845c362..469c198d 100644 --- a/src/Service/Step/Runner/ExploreStepsRunner.php +++ b/src/Service/Step/Runner/ExploreStepsRunner.php @@ -4,9 +4,7 @@ use Facebook\WebDriver\Remote\RemoteWebDriver; use Throwable; -use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; -use Tienvx\Bundle\MbtBundle\Model\BugInterface; use Tienvx\Bundle\MbtBundle\Model\DebugInterface; use Tienvx\Bundle\MbtBundle\Model\Model\RevisionInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; @@ -37,7 +35,7 @@ protected function catchException(callable $handleException, Throwable $throwabl // Last step cause the bug, we can't capture it. We capture it here. $this->steps[] = clone $step; } - $handleException($this->createBug($this->steps, $throwable->getMessage())); + $handleException($throwable, $this->steps); } protected function stop(?RemoteWebDriver $driver): void @@ -53,14 +51,4 @@ protected function runStep(StepInterface $step, RevisionInterface $revision, Rem return count($this->steps) < $this->config->getMaxSteps(); } - - protected function createBug(array $steps, string $message): BugInterface - { - $bug = new Bug(); - $bug->setTitle(''); - $bug->setSteps($steps); - $bug->setMessage($message); - - return $bug; - } } diff --git a/src/Service/Task/TaskHelper.php b/src/Service/Task/TaskHelper.php index b449b278..0af2a235 100644 --- a/src/Service/Task/TaskHelper.php +++ b/src/Service/Task/TaskHelper.php @@ -3,9 +3,11 @@ namespace Tienvx\Bundle\MbtBundle\Service\Task; use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException; +use Symfony\Component\Messenger\MessageBusInterface; +use Throwable; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; use Tienvx\Bundle\MbtBundle\Generator\GeneratorManagerInterface; -use Tienvx\Bundle\MbtBundle\Model\BugInterface; +use Tienvx\Bundle\MbtBundle\Message\CreateBugMessage; use Tienvx\Bundle\MbtBundle\Model\TaskInterface; use Tienvx\Bundle\MbtBundle\Repository\TaskRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; @@ -16,6 +18,7 @@ class TaskHelper implements TaskHelperInterface public function __construct( protected GeneratorManagerInterface $generatorManager, protected TaskRepositoryInterface $taskRepository, + protected MessageBusInterface $messageBus, protected ExploreStepsRunner $stepsRunner, protected ConfigInterface $config ) { @@ -41,8 +44,12 @@ public function run(int $taskId): void $this->stepsRunner->run( $this->generatorManager->getGenerator($this->config->getGenerator())->generate($task), $task, - function (BugInterface $bug) use ($task) { - $task->addBug($bug); + function (Throwable $throwable, array $steps) use ($taskId) { + $this->messageBus->dispatch(new CreateBugMessage( + $taskId, + $steps, + $throwable->getMessage() + )); } ); } finally { diff --git a/tests/MessageHandler/CreateBugMessageHandlerTest.php b/tests/MessageHandler/CreateBugMessageHandlerTest.php new file mode 100644 index 00000000..df6ff9c9 --- /dev/null +++ b/tests/MessageHandler/CreateBugMessageHandlerTest.php @@ -0,0 +1,77 @@ +config = $this->createMock(ConfigInterface::class); + $this->taskRepository = $this->createMock(TaskRepositoryInterface::class); + $this->handler = new CreateBugMessageHandler($this->config, $this->taskRepository); + $this->message = new CreateBugMessage( + 123, + [$this->createMock(StepInterface::class), $this->createMock(StepInterface::class)], + 'Something wrong' + ); + $this->task = new Task(); + $this->task->setId(123); + } + + public function testInvokeNoTask(): void + { + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Can not create bug for task 123: task not found'); + $this->taskRepository->expects($this->once())->method('find')->with(123)->willReturn(null); + call_user_func($this->handler, $this->message); + } + + public function testInvoke(): void + { + $this->taskRepository + ->expects($this->once()) + ->method('find') + ->with(123) + ->willReturn($this->task); + $this->config + ->expects($this->once()) + ->method('getDefaultBugTitle') + ->willReturn('Bug title'); + $this->taskRepository + ->expects($this->once()) + ->method('addBug') + ->with($this->task, $this->callback(function ($bug) { + return $bug instanceof Bug && + 'Bug title' === $bug->getTitle() && + $bug->getSteps() === $this->message->getSteps() && + 'Something wrong' === $bug->getMessage(); + })); + call_user_func($this->handler, $this->message); + } +} diff --git a/tests/MessageHandler/RunTaskMessageHandlerTest.php b/tests/MessageHandler/RunTaskMessageHandlerTest.php index d1e05d3d..06bbe563 100644 --- a/tests/MessageHandler/RunTaskMessageHandlerTest.php +++ b/tests/MessageHandler/RunTaskMessageHandlerTest.php @@ -2,6 +2,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\MessageHandler; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Tienvx\Bundle\MbtBundle\Message\RunTaskMessage; use Tienvx\Bundle\MbtBundle\MessageHandler\RunTaskMessageHandler; @@ -13,7 +14,7 @@ */ class RunTaskMessageHandlerTest extends TestCase { - protected TaskHelperInterface $taskHelper; + protected TaskHelperInterface|MockObject $taskHelper; protected RunTaskMessageHandler $handler; protected RunTaskMessage $message; diff --git a/tests/Reducer/HandlerTestCase.php b/tests/Reducer/HandlerTestCase.php index e902042a..c73f7fe3 100644 --- a/tests/Reducer/HandlerTestCase.php +++ b/tests/Reducer/HandlerTestCase.php @@ -3,6 +3,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Reducer; use Exception; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\Stub\Stub; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; @@ -11,23 +12,25 @@ use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Entity\Model\Revision; use Tienvx\Bundle\MbtBundle\Entity\Task; -use Tienvx\Bundle\MbtBundle\Exception\RuntimeException; use Tienvx\Bundle\MbtBundle\Exception\StepsNotConnectedException; +use Tienvx\Bundle\MbtBundle\Message\CreateBugMessage; use Tienvx\Bundle\MbtBundle\Message\ReduceBugMessage; use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; use Tienvx\Bundle\MbtBundle\Model\BugInterface; use Tienvx\Bundle\MbtBundle\Reducer\HandlerInterface; use Tienvx\Bundle\MbtBundle\Repository\BugRepositoryInterface; +use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Builder\StepsBuilderInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\BugStepsRunner; abstract class HandlerTestCase extends TestCase { protected HandlerInterface $handler; - protected BugRepositoryInterface $bugRepository; - protected MessageBusInterface $messageBus; - protected BugStepsRunner $stepsRunner; - protected StepsBuilderInterface $stepsBuilder; + protected BugRepositoryInterface|MockObject $bugRepository; + protected MessageBusInterface|MockObject $messageBus; + protected BugStepsRunner|MockObject $stepsRunner; + protected StepsBuilderInterface|MockObject $stepsBuilder; + protected ConfigInterface|MockObject $config; protected array $newSteps; protected Revision $revision; protected BugInterface $bug; @@ -38,6 +41,7 @@ protected function setUp(): void $this->messageBus = $this->createMock(MessageBusInterface::class); $this->stepsRunner = $this->createMock(BugStepsRunner::class); $this->stepsBuilder = $this->createMock(StepsBuilderInterface::class); + $this->config = $this->createMock(ConfigInterface::class); $this->newSteps = [ $this->createMock(StepInterface::class), $this->createMock(StepInterface::class), @@ -57,6 +61,7 @@ protected function setUp(): void $this->bug->setDebug(true); $this->revision = new Revision(); $task = new Task(); + $task->setId(123); $task->setModelRevision($this->revision); $this->bug->setTask($task); $this->stepsBuilder @@ -88,7 +93,7 @@ public function testHandleOldBug(): void /** * @dataProvider exceptionProvider */ - public function testHandle(?Throwable $exception, bool $updateSteps): void + public function testHandle(?Throwable $exception, bool $updateSteps, bool $createNewBug): void { $this->expectStepsBuilder($this->returnValue((fn () => yield from $this->newSteps)())); $this->stepsRunner->expects($this->once()) @@ -112,11 +117,34 @@ public function testHandle(?Throwable $exception, bool $updateSteps): void $this->messageBus ->expects($this->once()) ->method('dispatch') - ->with($this->isInstanceOf(ReduceBugMessage::class)) + ->with($this->callback(function ($message) { + return $message instanceof ReduceBugMessage && $message->getId() === $this->bug->getId(); + })) ->willReturn(new Envelope(new \stdClass())); } else { $this->bugRepository->expects($this->never())->method('updateSteps'); - $this->messageBus->expects($this->never())->method('dispatch'); + if ($exception) { + $this->config + ->expects($this->once()) + ->method('shouldCreateNewBugWhileReducing') + ->willReturn($createNewBug); + if ($createNewBug) { + $this->messageBus + ->expects($this->once()) + ->method('dispatch') + ->with($this->callback(function ($message) use ($exception) { + return $message instanceof CreateBugMessage && + $message->getMessage() === $exception->getMessage() && + $message->getSteps() === $this->newSteps && + 123 === $message->getTaskId(); + })) + ->willReturn(new Envelope(new \stdClass())); + } else { + $this->messageBus->expects($this->never())->method('dispatch'); + } + } else { + $this->messageBus->expects($this->never())->method('dispatch'); + } } $this->handler->handle($this->bug, 1, 2); $this->assertFalse($this->bug->isDebug()); @@ -125,9 +153,10 @@ public function testHandle(?Throwable $exception, bool $updateSteps): void public function exceptionProvider(): array { return [ - [null, false], - [new RuntimeException('Something else wrong'), false], - [new Exception('Something wrong'), true], + [null, false, false], + [new Exception('Something else wrong'), false, false], + [new Exception('Something else wrong'), false, true], + [new Exception('Something wrong'), true, false], ]; } diff --git a/tests/Reducer/Random/RandomHandlerTest.php b/tests/Reducer/Random/RandomHandlerTest.php index 3a1b866a..727ff67c 100644 --- a/tests/Reducer/Random/RandomHandlerTest.php +++ b/tests/Reducer/Random/RandomHandlerTest.php @@ -15,6 +15,7 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Task * @uses \Tienvx\Bundle\MbtBundle\Model\Bug\Step * @uses \Tienvx\Bundle\MbtBundle\Message\ReduceBugMessage + * @uses \Tienvx\Bundle\MbtBundle\Message\CreateBugMessage * @uses \Tienvx\Bundle\MbtBundle\Model\Progress * @uses \Tienvx\Bundle\MbtBundle\Model\Debug */ @@ -27,7 +28,8 @@ protected function setUp(): void $this->bugRepository, $this->messageBus, $this->stepsRunner, - $this->stepsBuilder + $this->stepsBuilder, + $this->config ); } } diff --git a/tests/Reducer/Split/SplitHandlerTest.php b/tests/Reducer/Split/SplitHandlerTest.php index 63515131..edbe06f7 100644 --- a/tests/Reducer/Split/SplitHandlerTest.php +++ b/tests/Reducer/Split/SplitHandlerTest.php @@ -15,6 +15,7 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Task * @uses \Tienvx\Bundle\MbtBundle\Model\Bug\Step * @uses \Tienvx\Bundle\MbtBundle\Message\ReduceBugMessage + * @uses \Tienvx\Bundle\MbtBundle\Message\CreateBugMessage * @uses \Tienvx\Bundle\MbtBundle\Model\Progress * @uses \Tienvx\Bundle\MbtBundle\Model\Debug */ @@ -27,7 +28,8 @@ protected function setUp(): void $this->bugRepository, $this->messageBus, $this->stepsRunner, - $this->stepsBuilder + $this->stepsBuilder, + $this->config ); } } diff --git a/tests/Repository/TaskRepositoryTest.php b/tests/Repository/TaskRepositoryTest.php index d436440e..82b8bcc2 100644 --- a/tests/Repository/TaskRepositoryTest.php +++ b/tests/Repository/TaskRepositoryTest.php @@ -6,7 +6,9 @@ use Doctrine\ORM\Decorator\EntityManagerDecorator; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\Persistence\ManagerRegistry; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Entity\Task; use Tienvx\Bundle\MbtBundle\Model\TaskInterface; use Tienvx\Bundle\MbtBundle\Repository\TaskRepository; @@ -17,10 +19,12 @@ * * @uses \Tienvx\Bundle\MbtBundle\Entity\Task * @uses \Tienvx\Bundle\MbtBundle\Model\Task + * @uses \Tienvx\Bundle\MbtBundle\Entity\Bug + * @uses \Tienvx\Bundle\MbtBundle\Model\Bug */ class TaskRepositoryTest extends TestCase { - protected EntityManagerDecorator $manager; + protected EntityManagerDecorator|MockObject $manager; protected TaskInterface $task; protected TaskRepositoryInterface $taskRepository; @@ -60,4 +64,13 @@ public function testStopRunningTask(): void $this->taskRepository->stopRunning($this->task); $this->assertFalse($this->task->isRunning()); } + + public function testAddBug(): void + { + $bug = new Bug(); + $this->assertEmpty($this->task->getBugs()); + $this->manager->expects($this->once())->method('flush'); + $this->taskRepository->addBug($this->task, $bug); + $this->assertSame([$bug], $this->task->getBugs()->toArray()); + } } diff --git a/tests/Service/Step/Runner/ExploreStepsRunnerTest.php b/tests/Service/Step/Runner/ExploreStepsRunnerTest.php index a066d4a9..6275eeaf 100644 --- a/tests/Service/Step/Runner/ExploreStepsRunnerTest.php +++ b/tests/Service/Step/Runner/ExploreStepsRunnerTest.php @@ -3,8 +3,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service\Step\Runner; use Exception; -use Tienvx\Bundle\MbtBundle\Model\Bug\StepInterface; -use Tienvx\Bundle\MbtBundle\Model\BugInterface; +use PHPUnit\Framework\MockObject\MockObject; use Tienvx\Bundle\MbtBundle\Model\DebugInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; use Tienvx\Bundle\MbtBundle\Service\Step\Runner\ExploreStepsRunner; @@ -23,7 +22,7 @@ */ class ExploreStepsRunnerTest extends StepsRunnerTestCase { - protected ConfigInterface $config; + protected ConfigInterface|MockObject $config; protected function setUp(): void { @@ -53,16 +52,7 @@ protected function assertHandlingException(Exception $exception, array $bugSteps $this->handleException ->expects($this->once()) ->method('__invoke') - ->with($this->callback(fn (object $bug) => $bug instanceof BugInterface && - '' === $bug->getTitle() && - $bug->getMessage() === $exception->getMessage() && - !array_udiff( - $bug->getSteps(), - $bugSteps, - fn (StepInterface $step1, StepInterface $step2) => $step1->getPlaces() === $step2->getPlaces() && - $step1->getTransition() === $step2->getTransition() && - $step1->getColor()->getValues() === $step2->getColor()->getValues() - ))); + ->with($exception, $bugSteps); } protected function assertRunSteps(array $steps = [], ?Exception $exception = null, int $maxSteps = 99): void diff --git a/tests/Service/Step/Runner/StepsRunnerTestCase.php b/tests/Service/Step/Runner/StepsRunnerTestCase.php index d062f074..d56fe260 100644 --- a/tests/Service/Step/Runner/StepsRunnerTestCase.php +++ b/tests/Service/Step/Runner/StepsRunnerTestCase.php @@ -23,10 +23,10 @@ abstract class StepsRunnerTestCase extends TestCase { protected array $steps; - protected StepRunnerInterface $stepRunner; + protected StepRunnerInterface|MockObject $stepRunner; protected StepsRunnerInterface $stepsRunner; - protected SelenoidHelperInterface $selenoidHelper; - protected RemoteWebDriver $driver; + protected SelenoidHelperInterface|MockObject $selenoidHelper; + protected RemoteWebDriver|MockObject $driver; protected Revision $revision; protected TaskInterface $task; protected BugInterface $bug; diff --git a/tests/Service/Task/TaskHelperTest.php b/tests/Service/Task/TaskHelperTest.php index beac0f02..4bd4821b 100644 --- a/tests/Service/Task/TaskHelperTest.php +++ b/tests/Service/Task/TaskHelperTest.php @@ -2,15 +2,18 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service\Task; +use Exception; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SingleColorPetrinet\Model\Color; +use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException; -use Tienvx\Bundle\MbtBundle\Entity\Bug; +use Symfony\Component\Messenger\MessageBusInterface; use Tienvx\Bundle\MbtBundle\Entity\Task; use Tienvx\Bundle\MbtBundle\Exception\UnexpectedValueException; use Tienvx\Bundle\MbtBundle\Generator\GeneratorInterface; use Tienvx\Bundle\MbtBundle\Generator\GeneratorManagerInterface; -use Tienvx\Bundle\MbtBundle\Model\BugInterface; +use Tienvx\Bundle\MbtBundle\Message\CreateBugMessage; use Tienvx\Bundle\MbtBundle\Model\TaskInterface; use Tienvx\Bundle\MbtBundle\Repository\TaskRepositoryInterface; use Tienvx\Bundle\MbtBundle\Service\ConfigInterface; @@ -29,15 +32,17 @@ * @uses \Tienvx\Bundle\MbtBundle\Model\Bug * @uses \Tienvx\Bundle\MbtBundle\Model\Bug\Step * @uses \Tienvx\Bundle\MbtBundle\Model\Debug + * @uses \Tienvx\Bundle\MbtBundle\Message\CreateBugMessage */ class TaskHelperTest extends TestCase { protected array $steps; - protected GeneratorManagerInterface $generatorManager; - protected TaskRepositoryInterface $taskRepository; - protected ExploreStepsRunner $stepsRunner; + protected GeneratorManagerInterface|MockObject $generatorManager; + protected TaskRepositoryInterface|MockObject $taskRepository; + protected MessageBusInterface|MockObject $messageBus; + protected ExploreStepsRunner|MockObject $stepsRunner; protected TaskHelperInterface $taskHelper; - protected ConfigInterface $config; + protected ConfigInterface|MockObject $config; protected TaskInterface $task; protected function setUp(): void @@ -50,11 +55,13 @@ protected function setUp(): void ]; $this->generatorManager = $this->createMock(GeneratorManagerInterface::class); $this->taskRepository = $this->createMock(TaskRepositoryInterface::class); + $this->messageBus = $this->createMock(MessageBusInterface::class); $this->stepsRunner = $this->createMock(ExploreStepsRunner::class); $this->config = $this->createMock(ConfigInterface::class); $this->taskHelper = new TaskHelper( $this->generatorManager, $this->taskRepository, + $this->messageBus, $this->stepsRunner, $this->config ); @@ -82,9 +89,9 @@ public function testRunTaskAlreadyRunning(): void } /** - * @dataProvider bugProvider + * @dataProvider exceptionProvider */ - public function testRun(?BugInterface $bug): void + public function testRun(?Exception $exception): void { $this->taskRepository->expects($this->once())->method('find')->with(123)->willReturn($this->task); $this->taskRepository->expects($this->once())->method('startRunning')->with($this->task); @@ -99,27 +106,36 @@ public function testRun(?BugInterface $bug): void ->with( $this->steps, $this->task, - $this->callback(function (callable $exceptionCallback) use ($bug) { - if ($bug) { - $exceptionCallback($bug); + $this->callback(function (callable $exceptionCallback) use ($exception) { + if ($exception) { + $exceptionCallback($exception, $this->steps); } return true; }) ); - $this->taskHelper->run(123); - if ($bug) { - $this->assertSame([$bug], $this->task->getBugs()->toArray()); + if ($exception) { + $this->messageBus + ->expects($this->once()) + ->method('dispatch') + ->with($this->callback(function ($message) use ($exception) { + return $message instanceof CreateBugMessage && + $message->getMessage() === $exception->getMessage() && + $message->getSteps() === $this->steps && + 123 === $message->getTaskId(); + })) + ->willReturn(new Envelope(new \stdClass())); } else { - $this->assertEmpty($this->task->getBugs()); + $this->messageBus->expects($this->never())->method('dispatch'); } + $this->taskHelper->run(123); } - public function bugProvider(): array + public function exceptionProvider(): array { return [ [null], - [new Bug()], + [new Exception('Something wrong')], ]; } } From a04a649e69362524cbb8b218ebd28a514b64c194 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 26 Jul 2022 22:06:22 +0700 Subject: [PATCH 83/85] Type hint mock object --- tests/Command/Assert/AssertCheckedCommandTest.php | 6 ++---- .../Command/Assert/AssertEditableCommandTest.php | 6 ++---- .../Assert/AssertElementNotPresentCommandTest.php | 4 +--- .../Assert/AssertElementPresentCommandTest.php | 4 +--- .../Assert/AssertNotCheckedCommandTest.php | 6 ++---- .../Assert/AssertNotEditableCommandTest.php | 6 ++---- .../Assert/AssertNotSelectedLabelCommandTest.php | 5 ++--- .../Assert/AssertNotSelectedValueCommandTest.php | 5 ++--- tests/Command/Assert/AssertNotTextCommandTest.php | 6 ++---- .../Assert/AssertSelectedLabelCommandTest.php | 5 ++--- .../Assert/AssertSelectedValueCommandTest.php | 5 ++--- tests/Command/Assert/AssertTextCommandTest.php | 6 ++---- tests/Command/Assert/AssertValueCommandTest.php | 6 ++---- tests/Command/Keyboard/SendKeysCommandTest.php | 8 +++----- tests/Command/Keyboard/TypeCommandTest.php | 10 ++++------ tests/Command/Mouse/AddSelectionCommandTest.php | 6 ++---- tests/Command/Mouse/CheckCommandTest.php | 8 +++----- tests/Command/Mouse/ClickAtCommandTest.php | 6 ++---- tests/Command/Mouse/ClickCommandTest.php | 6 ++---- tests/Command/Mouse/DoubleClickAtCommandTest.php | 6 ++---- tests/Command/Mouse/DoubleClickCommandTest.php | 6 ++---- .../Command/Mouse/RemoveSelectionCommandTest.php | 6 ++---- tests/Command/Mouse/UncheckCommandTest.php | 8 +++----- tests/Command/Store/StoreAttributeCommandTest.php | 6 ++---- .../Store/StoreElementCountCommandTest.php | 4 +--- tests/Command/Store/StoreTextCommandTest.php | 6 ++---- tests/Command/Store/StoreValueCommandTest.php | 6 ++---- .../Wait/WaitForElementEditableCommandTest.php | 6 ++---- .../Wait/WaitForElementNotEditableCommandTest.php | 6 ++---- .../Wait/WaitForElementNotPresentCommandTest.php | 3 +-- .../Wait/WaitForElementNotVisibleCommandTest.php | 6 ++---- .../Wait/WaitForElementPresentCommandTest.php | 4 +--- .../Wait/WaitForElementVisibleCommandTest.php | 6 ++---- tests/Command/Window/SelectFrameCommandTest.php | 6 ++---- tests/EventListener/EntitySubscriberTest.php | 15 +++++++++++---- .../RecordVideoMessageHandlerTest.php | 3 ++- .../ReduceBugMessageHandlerTest.php | 3 ++- .../ReduceStepsMessageHandlerTest.php | 3 ++- .../ReportBugMessageHandlerTest.php | 3 ++- tests/Reducer/DispatcherTestCase.php | 3 ++- tests/Reducer/Random/RandomReducerTest.php | 5 +++-- tests/Reducer/Split/SplitReducerTest.php | 5 +++-- tests/Repository/BugRepositoryTest.php | 3 ++- tests/Service/Bug/BugHelperTest.php | 13 +++++++------ .../Step/Builder/PetrinetDomainLogicTest.php | 7 ++++--- tests/Service/Step/Runner/StepRunnerTest.php | 5 +++-- 46 files changed, 111 insertions(+), 156 deletions(-) diff --git a/tests/Command/Assert/AssertCheckedCommandTest.php b/tests/Command/Assert/AssertCheckedCommandTest.php index 8b49e288..34a7c480 100644 --- a/tests/Command/Assert/AssertCheckedCommandTest.php +++ b/tests/Command/Assert/AssertCheckedCommandTest.php @@ -4,7 +4,6 @@ use Exception; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Assert\AssertCheckedCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -34,8 +33,7 @@ public function testRun(bool $isSelected, ?Exception $exception): void if ($exception) { $this->expectExceptionObject($exception); } - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('isSelected')->willReturn($isSelected); + $this->element->expects($this->once())->method('isSelected')->willReturn($isSelected); $this->driver ->expects($this->once()) ->method('findElement') @@ -44,7 +42,7 @@ public function testRun(bool $isSelected, ?Exception $exception): void && 'css selector' === $selector->getMechanism() && '.term-and-condition' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $this->command->run('css=.term-and-condition', null, $this->values, $this->driver); } diff --git a/tests/Command/Assert/AssertEditableCommandTest.php b/tests/Command/Assert/AssertEditableCommandTest.php index 0c10fd0e..badfd9b4 100644 --- a/tests/Command/Assert/AssertEditableCommandTest.php +++ b/tests/Command/Assert/AssertEditableCommandTest.php @@ -4,7 +4,6 @@ use Exception; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Assert\AssertEditableCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -34,7 +33,6 @@ public function testRun(bool $enabled, bool $readonly, ?Exception $exception): v if ($exception) { $this->expectExceptionObject($exception); } - $element = $this->createMock(WebDriverElement::class); $this->driver ->expects($this->once()) ->method('findElement') @@ -43,13 +41,13 @@ public function testRun(bool $enabled, bool $readonly, ?Exception $exception): v && 'name' === $selector->getMechanism() && 'username' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $this->driver ->expects($this->once()) ->method('executeScript') ->with( 'return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', - [$element] + [$this->element] ) ->willReturn((object) ['enabled' => $enabled, 'readonly' => $readonly]); $this->command->run('name=username', null, $this->values, $this->driver); diff --git a/tests/Command/Assert/AssertElementNotPresentCommandTest.php b/tests/Command/Assert/AssertElementNotPresentCommandTest.php index 46676684..6eab0095 100644 --- a/tests/Command/Assert/AssertElementNotPresentCommandTest.php +++ b/tests/Command/Assert/AssertElementNotPresentCommandTest.php @@ -4,7 +4,6 @@ use Exception; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Assert\AssertElementNotPresentCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -34,7 +33,6 @@ public function testRun(int $count, ?Exception $exception): void if ($exception) { $this->expectExceptionObject($exception); } - $element = $this->createMock(WebDriverElement::class); $this->driver ->expects($this->once()) ->method('findElements') @@ -45,7 +43,7 @@ public function testRun(int $count, ?Exception $exception): void && '.cart' === $selector->getValue(); }) ) - ->willReturn(array_fill(0, $count, $element)); + ->willReturn(array_fill(0, $count, $this->element)); $this->command->run('css=.cart', null, $this->values, $this->driver); } diff --git a/tests/Command/Assert/AssertElementPresentCommandTest.php b/tests/Command/Assert/AssertElementPresentCommandTest.php index ba71f049..81fbb7e3 100644 --- a/tests/Command/Assert/AssertElementPresentCommandTest.php +++ b/tests/Command/Assert/AssertElementPresentCommandTest.php @@ -4,7 +4,6 @@ use Exception; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Assert\AssertElementPresentCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -34,7 +33,6 @@ public function testRun(int $count, ?Exception $exception): void if ($exception) { $this->expectExceptionObject($exception); } - $element = $this->createMock(WebDriverElement::class); $this->driver ->expects($this->once()) ->method('findElements') @@ -45,7 +43,7 @@ public function testRun(int $count, ?Exception $exception): void && '.cart' === $selector->getValue(); }) ) - ->willReturn(array_fill(0, $count, $element)); + ->willReturn(array_fill(0, $count, $this->element)); $this->command->run('css=.cart', null, $this->values, $this->driver); } diff --git a/tests/Command/Assert/AssertNotCheckedCommandTest.php b/tests/Command/Assert/AssertNotCheckedCommandTest.php index db5eb899..60050431 100644 --- a/tests/Command/Assert/AssertNotCheckedCommandTest.php +++ b/tests/Command/Assert/AssertNotCheckedCommandTest.php @@ -4,7 +4,6 @@ use Exception; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Assert\AssertNotCheckedCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -34,8 +33,7 @@ public function testRun(bool $isSelected, ?Exception $exception): void if ($exception) { $this->expectExceptionObject($exception); } - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('isSelected')->willReturn($isSelected); + $this->element->expects($this->once())->method('isSelected')->willReturn($isSelected); $this->driver ->expects($this->once()) ->method('findElement') @@ -44,7 +42,7 @@ public function testRun(bool $isSelected, ?Exception $exception): void && 'css selector' === $selector->getMechanism() && '.term-and-condition' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $this->command->run('css=.term-and-condition', null, $this->values, $this->driver); } diff --git a/tests/Command/Assert/AssertNotEditableCommandTest.php b/tests/Command/Assert/AssertNotEditableCommandTest.php index f8fc5ce8..3e0b5572 100644 --- a/tests/Command/Assert/AssertNotEditableCommandTest.php +++ b/tests/Command/Assert/AssertNotEditableCommandTest.php @@ -4,7 +4,6 @@ use Exception; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Assert\AssertNotEditableCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -34,7 +33,6 @@ public function testRun(bool $enabled, bool $readonly, ?Exception $exception): v if ($exception) { $this->expectExceptionObject($exception); } - $element = $this->createMock(WebDriverElement::class); $this->driver ->expects($this->once()) ->method('findElement') @@ -43,13 +41,13 @@ public function testRun(bool $enabled, bool $readonly, ?Exception $exception): v && 'name' === $selector->getMechanism() && 'username' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $this->driver ->expects($this->once()) ->method('executeScript') ->with( 'return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', - [$element] + [$this->element] ) ->willReturn((object) ['enabled' => $enabled, 'readonly' => $readonly]); $this->command->run('name=username', null, $this->values, $this->driver); diff --git a/tests/Command/Assert/AssertNotSelectedLabelCommandTest.php b/tests/Command/Assert/AssertNotSelectedLabelCommandTest.php index ca0a50c8..07c1fbbb 100644 --- a/tests/Command/Assert/AssertNotSelectedLabelCommandTest.php +++ b/tests/Command/Assert/AssertNotSelectedLabelCommandTest.php @@ -35,7 +35,6 @@ public function testRun(string $actual, ?Exception $exception): void if ($exception) { $this->expectExceptionObject($exception); } - $element = $this->createMock(WebDriverElement::class); $this->driver ->expects($this->once()) ->method('findElement') @@ -44,13 +43,13 @@ public function testRun(string $actual, ?Exception $exception): void && 'partial link text' === $selector->getMechanism() && 'Language' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $option = $this->createMock(WebDriverElement::class); $option->expects($this->once())->method('getText')->willReturn($actual); $select = $this->createMock(WebDriverSelect::class); $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); $command = $this->createPartialMock(AssertNotSelectedLabelCommand::class, ['getSelect']); - $command->expects($this->once())->method('getSelect')->with($element)->willReturn($select); + $command->expects($this->once())->method('getSelect')->with($this->element)->willReturn($select); $command->run('partialLinkText=Language', 'United Kingdom', $this->values, $this->driver); } diff --git a/tests/Command/Assert/AssertNotSelectedValueCommandTest.php b/tests/Command/Assert/AssertNotSelectedValueCommandTest.php index 846ebf00..8e56755e 100644 --- a/tests/Command/Assert/AssertNotSelectedValueCommandTest.php +++ b/tests/Command/Assert/AssertNotSelectedValueCommandTest.php @@ -35,7 +35,6 @@ public function testRun(string $actual, ?Exception $exception): void if ($exception) { $this->expectExceptionObject($exception); } - $element = $this->createMock(WebDriverElement::class); $this->driver ->expects($this->once()) ->method('findElement') @@ -44,13 +43,13 @@ public function testRun(string $actual, ?Exception $exception): void && 'partial link text' === $selector->getMechanism() && 'Language' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $option = $this->createMock(WebDriverElement::class); $option->expects($this->once())->method('getAttribute')->with('value')->willReturn($actual); $select = $this->createMock(WebDriverSelect::class); $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); $command = $this->createPartialMock(AssertNotSelectedValueCommand::class, ['getSelect']); - $command->expects($this->once())->method('getSelect')->with($element)->willReturn($select); + $command->expects($this->once())->method('getSelect')->with($this->element)->willReturn($select); $command->run('partialLinkText=Language', 'en_GB', $this->values, $this->driver); } diff --git a/tests/Command/Assert/AssertNotTextCommandTest.php b/tests/Command/Assert/AssertNotTextCommandTest.php index 4f6df886..06d530da 100644 --- a/tests/Command/Assert/AssertNotTextCommandTest.php +++ b/tests/Command/Assert/AssertNotTextCommandTest.php @@ -4,7 +4,6 @@ use Exception; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Assert\AssertNotTextCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -34,8 +33,7 @@ public function testRun(string $actual, ?Exception $exception): void if ($exception) { $this->expectExceptionObject($exception); } - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('getText')->willReturn($actual); + $this->element->expects($this->once())->method('getText')->willReturn($actual); $this->driver ->expects($this->once()) ->method('findElement') @@ -44,7 +42,7 @@ public function testRun(string $actual, ?Exception $exception): void && 'xpath' === $selector->getMechanism() && '//h4[@href="#"]' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $this->command->run('xpath=//h4[@href="#"]', 'Welcome to our store', $this->values, $this->driver); } diff --git a/tests/Command/Assert/AssertSelectedLabelCommandTest.php b/tests/Command/Assert/AssertSelectedLabelCommandTest.php index 4bb9e888..dd046ca8 100644 --- a/tests/Command/Assert/AssertSelectedLabelCommandTest.php +++ b/tests/Command/Assert/AssertSelectedLabelCommandTest.php @@ -35,7 +35,6 @@ public function testRun(string $actual, ?Exception $exception): void if ($exception) { $this->expectExceptionObject($exception); } - $element = $this->createMock(WebDriverElement::class); $this->driver ->expects($this->once()) ->method('findElement') @@ -44,13 +43,13 @@ public function testRun(string $actual, ?Exception $exception): void && 'partial link text' === $selector->getMechanism() && 'Language' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $option = $this->createMock(WebDriverElement::class); $option->expects($this->once())->method('getText')->willReturn($actual); $select = $this->createMock(WebDriverSelect::class); $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); $command = $this->createPartialMock(AssertSelectedLabelCommand::class, ['getSelect']); - $command->expects($this->once())->method('getSelect')->with($element)->willReturn($select); + $command->expects($this->once())->method('getSelect')->with($this->element)->willReturn($select); $command->run('partialLinkText=Language', 'United Kingdom', $this->values, $this->driver); } diff --git a/tests/Command/Assert/AssertSelectedValueCommandTest.php b/tests/Command/Assert/AssertSelectedValueCommandTest.php index 835d19d6..7a697f46 100644 --- a/tests/Command/Assert/AssertSelectedValueCommandTest.php +++ b/tests/Command/Assert/AssertSelectedValueCommandTest.php @@ -35,7 +35,6 @@ public function testRun(string $actual, ?Exception $exception): void if ($exception) { $this->expectExceptionObject($exception); } - $element = $this->createMock(WebDriverElement::class); $this->driver ->expects($this->once()) ->method('findElement') @@ -44,13 +43,13 @@ public function testRun(string $actual, ?Exception $exception): void && 'partial link text' === $selector->getMechanism() && 'Language' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $option = $this->createMock(WebDriverElement::class); $option->expects($this->once())->method('getAttribute')->with('value')->willReturn($actual); $select = $this->createMock(WebDriverSelect::class); $select->expects($this->once())->method('getFirstSelectedOption')->willReturn($option); $command = $this->createPartialMock(AssertSelectedValueCommand::class, ['getSelect']); - $command->expects($this->once())->method('getSelect')->with($element)->willReturn($select); + $command->expects($this->once())->method('getSelect')->with($this->element)->willReturn($select); $command->run('partialLinkText=Language', 'en_GB', $this->values, $this->driver); } diff --git a/tests/Command/Assert/AssertTextCommandTest.php b/tests/Command/Assert/AssertTextCommandTest.php index c878adbd..a07c32a0 100644 --- a/tests/Command/Assert/AssertTextCommandTest.php +++ b/tests/Command/Assert/AssertTextCommandTest.php @@ -4,7 +4,6 @@ use Exception; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Assert\AssertTextCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -34,8 +33,7 @@ public function testRun(string $actual, ?Exception $exception): void if ($exception) { $this->expectExceptionObject($exception); } - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('getText')->willReturn($actual); + $this->element->expects($this->once())->method('getText')->willReturn($actual); $this->driver ->expects($this->once()) ->method('findElement') @@ -44,7 +42,7 @@ public function testRun(string $actual, ?Exception $exception): void && 'xpath' === $selector->getMechanism() && '//h4[@href="#"]' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $this->command->run('xpath=//h4[@href="#"]', 'Welcome to our store', $this->values, $this->driver); } diff --git a/tests/Command/Assert/AssertValueCommandTest.php b/tests/Command/Assert/AssertValueCommandTest.php index 0d8e1760..d2290004 100644 --- a/tests/Command/Assert/AssertValueCommandTest.php +++ b/tests/Command/Assert/AssertValueCommandTest.php @@ -4,7 +4,6 @@ use Exception; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Assert\AssertValueCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -34,8 +33,7 @@ public function testRun(string $actual, ?Exception $exception): void if ($exception) { $this->expectExceptionObject($exception); } - $element = $this->createMock(WebDriverElement::class); - $element + $this->element ->expects($this->once()) ->method('getAttribute') ->with('value') @@ -48,7 +46,7 @@ public function testRun(string $actual, ?Exception $exception): void && 'css selector' === $selector->getMechanism() && '.quality' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $this->command->run('css=.quality', '14', $this->values, $this->driver); } diff --git a/tests/Command/Keyboard/SendKeysCommandTest.php b/tests/Command/Keyboard/SendKeysCommandTest.php index 2000a54e..6c6991fb 100644 --- a/tests/Command/Keyboard/SendKeysCommandTest.php +++ b/tests/Command/Keyboard/SendKeysCommandTest.php @@ -3,7 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Command\Keyboard; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Keyboard\SendKeysCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -27,14 +26,13 @@ protected function createCommand(): SendKeysCommand public function testRun(): void { - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('click')->willReturnSelf(); - $element->expects($this->once())->method('sendKeys')->with('123'); + $this->element->expects($this->once())->method('click')->willReturnSelf(); + $this->element->expects($this->once())->method('sendKeys')->with('123'); $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { return $selector instanceof WebDriverBy && 'css selector' === $selector->getMechanism() && '.quantity' === $selector->getValue(); - }))->willReturn($element); + }))->willReturn($this->element); $this->command->run('css=.quantity', '123', $this->values, $this->driver); } diff --git a/tests/Command/Keyboard/TypeCommandTest.php b/tests/Command/Keyboard/TypeCommandTest.php index d0623c33..01fe80af 100644 --- a/tests/Command/Keyboard/TypeCommandTest.php +++ b/tests/Command/Keyboard/TypeCommandTest.php @@ -3,7 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Command\Keyboard; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Keyboard\TypeCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -27,10 +26,9 @@ protected function createCommand(): TypeCommand public function testRun(): void { - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('click')->willReturnSelf(); - $element->expects($this->once())->method('clear')->willReturnSelf(); - $element->expects($this->once())->method('sendKeys')->with('20 years old'); + $this->element->expects($this->once())->method('click')->willReturnSelf(); + $this->element->expects($this->once())->method('clear')->willReturnSelf(); + $this->element->expects($this->once())->method('sendKeys')->with('20 years old'); $this->driver ->expects($this->once()) ->method('findElement') @@ -39,7 +37,7 @@ public function testRun(): void && 'name' === $selector->getMechanism() && 'age' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $this->command->run('name=age', '20 years old', $this->values, $this->driver); } diff --git a/tests/Command/Mouse/AddSelectionCommandTest.php b/tests/Command/Mouse/AddSelectionCommandTest.php index 59a09b2c..a3bfdc70 100644 --- a/tests/Command/Mouse/AddSelectionCommandTest.php +++ b/tests/Command/Mouse/AddSelectionCommandTest.php @@ -3,7 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Command\Mouse; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverSelect; use Tienvx\Bundle\MbtBundle\Command\Mouse\AddSelectionCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -32,7 +31,6 @@ protected function createCommand(): AddSelectionCommand */ public function testRun(string $value, string $method, string|int $with): void { - $element = $this->createMock(WebDriverElement::class); $this->driver ->expects($this->once()) ->method('findElement') @@ -41,11 +39,11 @@ public function testRun(string $value, string $method, string|int $with): void && 'partial link text' === $selector->getMechanism() && 'Language' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $select = $this->createMock(WebDriverSelect::class); $select->expects($this->once())->method($method)->with($with); $command = $this->createPartialMock(AddSelectionCommand::class, ['getSelect']); - $command->expects($this->once())->method('getSelect')->with($element)->willReturn($select); + $command->expects($this->once())->method('getSelect')->with($this->element)->willReturn($select); $command->run('partialLinkText=Language', $value, $this->values, $this->driver); } diff --git a/tests/Command/Mouse/CheckCommandTest.php b/tests/Command/Mouse/CheckCommandTest.php index f752d3ff..b15f8f41 100644 --- a/tests/Command/Mouse/CheckCommandTest.php +++ b/tests/Command/Mouse/CheckCommandTest.php @@ -3,7 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Command\Mouse; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Mouse\CheckCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -30,9 +29,8 @@ protected function createCommand(): CheckCommand */ public function testRun(bool $selected, bool $checked): void { - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('isSelected')->willReturn($selected); - $element->expects($this->exactly(+$checked))->method('click'); + $this->element->expects($this->once())->method('isSelected')->willReturn($selected); + $this->element->expects($this->exactly(+$checked))->method('click'); $this->driver ->expects($this->once()) ->method('findElement') @@ -41,7 +39,7 @@ public function testRun(bool $selected, bool $checked): void && 'id' === $selector->getMechanism() && 'language' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $this->command->run('id=language', null, $this->values, $this->driver); } diff --git a/tests/Command/Mouse/ClickAtCommandTest.php b/tests/Command/Mouse/ClickAtCommandTest.php index 9f84acc5..3ed31924 100644 --- a/tests/Command/Mouse/ClickAtCommandTest.php +++ b/tests/Command/Mouse/ClickAtCommandTest.php @@ -4,7 +4,6 @@ use Facebook\WebDriver\Interactions\WebDriverActions; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Mouse\ClickAtCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -29,14 +28,13 @@ protected function createCommand(): ClickAtCommand public function testRun(): void { - $element = $this->createMock(WebDriverElement::class); $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { return $selector instanceof WebDriverBy && 'id' === $selector->getMechanism() && 'cart' === $selector->getValue(); - }))->willReturn($element); + }))->willReturn($this->element); $action = $this->createMock(WebDriverActions::class); - $action->expects($this->once())->method('moveToElement')->with($element, 5, 10)->willReturnSelf(); + $action->expects($this->once())->method('moveToElement')->with($this->element, 5, 10)->willReturnSelf(); $action->expects($this->once())->method('click')->willReturnSelf(); $action->expects($this->once())->method('perform'); $this->driver->expects($this->once())->method('action')->willReturn($action); diff --git a/tests/Command/Mouse/ClickCommandTest.php b/tests/Command/Mouse/ClickCommandTest.php index 6e04a4ed..083e32ec 100644 --- a/tests/Command/Mouse/ClickCommandTest.php +++ b/tests/Command/Mouse/ClickCommandTest.php @@ -3,7 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Command\Mouse; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Mouse\ClickCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -27,13 +26,12 @@ protected function createCommand(): ClickCommand public function testRun(): void { - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('click'); + $this->element->expects($this->once())->method('click'); $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { return $selector instanceof WebDriverBy && 'id' === $selector->getMechanism() && 'add-to-cart' === $selector->getValue(); - }))->willReturn($element); + }))->willReturn($this->element); $this->command->run('id=add-to-cart', null, $this->values, $this->driver); } diff --git a/tests/Command/Mouse/DoubleClickAtCommandTest.php b/tests/Command/Mouse/DoubleClickAtCommandTest.php index 2fb71515..c71396f4 100644 --- a/tests/Command/Mouse/DoubleClickAtCommandTest.php +++ b/tests/Command/Mouse/DoubleClickAtCommandTest.php @@ -4,7 +4,6 @@ use Facebook\WebDriver\Interactions\WebDriverActions; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Mouse\DoubleClickAtCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -29,14 +28,13 @@ protected function createCommand(): DoubleClickAtCommand public function testRun(): void { - $element = $this->createMock(WebDriverElement::class); $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { return $selector instanceof WebDriverBy && 'id' === $selector->getMechanism() && 'cart' === $selector->getValue(); - }))->willReturn($element); + }))->willReturn($this->element); $action = $this->createMock(WebDriverActions::class); - $action->expects($this->once())->method('moveToElement')->with($element, 5, 10)->willReturnSelf(); + $action->expects($this->once())->method('moveToElement')->with($this->element, 5, 10)->willReturnSelf(); $action->expects($this->once())->method('doubleClick')->willReturnSelf(); $action->expects($this->once())->method('perform'); $this->driver->expects($this->once())->method('action')->willReturn($action); diff --git a/tests/Command/Mouse/DoubleClickCommandTest.php b/tests/Command/Mouse/DoubleClickCommandTest.php index 2d4c3fd0..60ac8621 100644 --- a/tests/Command/Mouse/DoubleClickCommandTest.php +++ b/tests/Command/Mouse/DoubleClickCommandTest.php @@ -4,7 +4,6 @@ use Facebook\WebDriver\Interactions\WebDriverActions; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Mouse\DoubleClickCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -28,14 +27,13 @@ protected function createCommand(): DoubleClickCommand public function testRun(): void { - $element = $this->createMock(WebDriverElement::class); $this->driver->expects($this->once())->method('findElement')->with($this->callback(function ($selector) { return $selector instanceof WebDriverBy && 'id' === $selector->getMechanism() && 'cart' === $selector->getValue(); - }))->willReturn($element); + }))->willReturn($this->element); $action = $this->createMock(WebDriverActions::class); - $action->expects($this->once())->method('doubleClick')->with($element)->willReturnSelf(); + $action->expects($this->once())->method('doubleClick')->with($this->element)->willReturnSelf(); $action->expects($this->once())->method('perform'); $this->driver->expects($this->once())->method('action')->willReturn($action); $this->command->run('id=cart', null, $this->values, $this->driver); diff --git a/tests/Command/Mouse/RemoveSelectionCommandTest.php b/tests/Command/Mouse/RemoveSelectionCommandTest.php index ddd005d1..de14048d 100644 --- a/tests/Command/Mouse/RemoveSelectionCommandTest.php +++ b/tests/Command/Mouse/RemoveSelectionCommandTest.php @@ -3,7 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Command\Mouse; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverSelect; use Tienvx\Bundle\MbtBundle\Command\Mouse\RemoveSelectionCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -32,7 +31,6 @@ protected function createCommand(): RemoveSelectionCommand */ public function testRun(string $value, string $method, string|int $with): void { - $element = $this->createMock(WebDriverElement::class); $this->driver ->expects($this->once()) ->method('findElement') @@ -41,11 +39,11 @@ public function testRun(string $value, string $method, string|int $with): void && 'partial link text' === $selector->getMechanism() && 'Language' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $select = $this->createMock(WebDriverSelect::class); $select->expects($this->once())->method($method)->with($with); $command = $this->createPartialMock(RemoveSelectionCommand::class, ['getSelect']); - $command->expects($this->once())->method('getSelect')->with($element)->willReturn($select); + $command->expects($this->once())->method('getSelect')->with($this->element)->willReturn($select); $command->run('partialLinkText=Language', $value, $this->values, $this->driver); } diff --git a/tests/Command/Mouse/UncheckCommandTest.php b/tests/Command/Mouse/UncheckCommandTest.php index 759293d0..6c5fb551 100644 --- a/tests/Command/Mouse/UncheckCommandTest.php +++ b/tests/Command/Mouse/UncheckCommandTest.php @@ -3,7 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Command\Mouse; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Mouse\UncheckCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -30,9 +29,8 @@ protected function createCommand(): UncheckCommand */ public function testRun(bool $selected, bool $unchecked): void { - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('isSelected')->willReturn($selected); - $element->expects($this->exactly(+$unchecked))->method('click'); + $this->element->expects($this->once())->method('isSelected')->willReturn($selected); + $this->element->expects($this->exactly(+$unchecked))->method('click'); $this->driver ->expects($this->once()) ->method('findElement') @@ -41,7 +39,7 @@ public function testRun(bool $selected, bool $unchecked): void && 'id' === $selector->getMechanism() && 'language' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $this->command->run('id=language', null, $this->values, $this->driver); } diff --git a/tests/Command/Store/StoreAttributeCommandTest.php b/tests/Command/Store/StoreAttributeCommandTest.php index 5479d4c4..1dd788fa 100644 --- a/tests/Command/Store/StoreAttributeCommandTest.php +++ b/tests/Command/Store/StoreAttributeCommandTest.php @@ -3,7 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Command\Store; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Store\StoreAttributeCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -31,8 +30,7 @@ public function testRun(): void ->expects($this->once()) ->method('setValue') ->with('readmoreLink', 'http://example.com'); - $element = $this->createMock(WebDriverElement::class); - $element + $this->element ->expects($this->once()) ->method('getAttribute') ->with('href') @@ -45,7 +43,7 @@ public function testRun(): void && 'css selector' === $selector->getMechanism() && '.readmore' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $this->command->run('css=.readmore@href', 'readmoreLink', $this->values, $this->driver); } diff --git a/tests/Command/Store/StoreElementCountCommandTest.php b/tests/Command/Store/StoreElementCountCommandTest.php index fdafdef4..2ff98d34 100644 --- a/tests/Command/Store/StoreElementCountCommandTest.php +++ b/tests/Command/Store/StoreElementCountCommandTest.php @@ -3,7 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Command\Store; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Store\StoreElementCountCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -28,12 +27,11 @@ protected function createCommand(): StoreElementCountCommand public function testRun(): void { $this->values->expects($this->once())->method('setValue')->with('itemCount', 2); - $element = $this->createMock(WebDriverElement::class); $this->driver->expects($this->once())->method('findElements')->with($this->callback(function ($selector) { return $selector instanceof WebDriverBy && 'css selector' === $selector->getMechanism() && '.item' === $selector->getValue(); - }))->willReturn([$element, $element]); + }))->willReturn([$this->element, $this->element]); $this->command->run('css=.item', 'itemCount', $this->values, $this->driver); } diff --git a/tests/Command/Store/StoreTextCommandTest.php b/tests/Command/Store/StoreTextCommandTest.php index d39f14d2..3e528114 100644 --- a/tests/Command/Store/StoreTextCommandTest.php +++ b/tests/Command/Store/StoreTextCommandTest.php @@ -3,7 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Command\Store; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Store\StoreTextCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -31,8 +30,7 @@ public function testRun(): void ->expects($this->once()) ->method('setValue') ->with('headLine', 'Welcome to our site'); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('getText')->willReturn('Welcome to our site'); + $this->element->expects($this->once())->method('getText')->willReturn('Welcome to our site'); $this->driver ->expects($this->once()) ->method('findElement') @@ -41,7 +39,7 @@ public function testRun(): void && 'css selector' === $selector->getMechanism() && '.head-line' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $this->command->run('css=.head-line', 'headLine', $this->values, $this->driver); } diff --git a/tests/Command/Store/StoreValueCommandTest.php b/tests/Command/Store/StoreValueCommandTest.php index e47bb7f5..de882984 100644 --- a/tests/Command/Store/StoreValueCommandTest.php +++ b/tests/Command/Store/StoreValueCommandTest.php @@ -3,7 +3,6 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Command\Store; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Tienvx\Bundle\MbtBundle\Command\Store\StoreValueCommand; use Tienvx\Bundle\MbtBundle\Tests\Command\CommandTestCase; @@ -28,8 +27,7 @@ protected function createCommand(): StoreValueCommand public function testRun(): void { $this->values->expects($this->once())->method('setValue')->with('age', 23); - $element = $this->createMock(WebDriverElement::class); - $element->expects($this->once())->method('getAttribute')->with('value')->willReturn(23); + $this->element->expects($this->once())->method('getAttribute')->with('value')->willReturn(23); $this->driver ->expects($this->once()) ->method('findElement') @@ -38,7 +36,7 @@ public function testRun(): void && 'css selector' === $selector->getMechanism() && '.age' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $this->command->run('css=.age', 'age', $this->values, $this->driver); } diff --git a/tests/Command/Wait/WaitForElementEditableCommandTest.php b/tests/Command/Wait/WaitForElementEditableCommandTest.php index 4da8613f..4ce01d45 100644 --- a/tests/Command/Wait/WaitForElementEditableCommandTest.php +++ b/tests/Command/Wait/WaitForElementEditableCommandTest.php @@ -4,7 +4,6 @@ use Closure; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverWait; use Tienvx\Bundle\MbtBundle\Command\Wait\WaitForElementEditableCommand; @@ -25,7 +24,6 @@ protected function createCommand(): WaitForElementEditableCommand */ public function testRun(bool $enabled, bool $readonly, bool $editable): void { - $element = $this->createMock(WebDriverElement::class); $this->driver ->expects($this->once()) ->method('findElement') @@ -34,7 +32,7 @@ public function testRun(bool $enabled, bool $readonly, bool $editable): void && 'id' === $selector->getMechanism() && 'name' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $wait = $this->createMock(WebDriverWait::class); $wait ->expects($this->once()) @@ -48,7 +46,7 @@ public function testRun(bool $enabled, bool $readonly, bool $editable): void ->method('executeScript') ->with( 'return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', - [$element] + [$this->element] ) ->willReturn((object) ['enabled' => $enabled, 'readonly' => $readonly]); $this->command->run('id=name', 123, $this->values, $this->driver); diff --git a/tests/Command/Wait/WaitForElementNotEditableCommandTest.php b/tests/Command/Wait/WaitForElementNotEditableCommandTest.php index f7dc8e95..d4c70123 100644 --- a/tests/Command/Wait/WaitForElementNotEditableCommandTest.php +++ b/tests/Command/Wait/WaitForElementNotEditableCommandTest.php @@ -4,7 +4,6 @@ use Closure; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverWait; use Tienvx\Bundle\MbtBundle\Command\Wait\WaitForElementNotEditableCommand; @@ -25,7 +24,6 @@ protected function createCommand(): WaitForElementNotEditableCommand */ public function testRun(bool $enabled, bool $readonly, bool $editable): void { - $element = $this->createMock(WebDriverElement::class); $this->driver ->expects($this->once()) ->method('findElement') @@ -34,7 +32,7 @@ public function testRun(bool $enabled, bool $readonly, bool $editable): void && 'id' === $selector->getMechanism() && 'name' === $selector->getValue(); })) - ->willReturn($element); + ->willReturn($this->element); $wait = $this->createMock(WebDriverWait::class); $wait ->expects($this->once()) @@ -48,7 +46,7 @@ public function testRun(bool $enabled, bool $readonly, bool $editable): void ->method('executeScript') ->with( 'return { enabled: !arguments[0].disabled, readonly: arguments[0].readOnly };', - [$element] + [$this->element] ) ->willReturn((object) ['enabled' => $enabled, 'readonly' => $readonly]); $this->command->run('id=name', 123, $this->values, $this->driver); diff --git a/tests/Command/Wait/WaitForElementNotPresentCommandTest.php b/tests/Command/Wait/WaitForElementNotPresentCommandTest.php index 134c768c..8050fc26 100644 --- a/tests/Command/Wait/WaitForElementNotPresentCommandTest.php +++ b/tests/Command/Wait/WaitForElementNotPresentCommandTest.php @@ -4,7 +4,6 @@ use Facebook\WebDriver\Exception\StaleElementReferenceException; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverExpectedCondition; use Facebook\WebDriver\WebDriverWait; use Tienvx\Bundle\MbtBundle\Command\Wait\WaitForElementNotPresentCommand; @@ -26,7 +25,7 @@ protected function createCommand(): WaitForElementNotPresentCommand */ public function testRun(int $elementsCount, bool $stale): void { - $elements = array_fill(0, $elementsCount, $this->createMock(WebDriverElement::class)); + $elements = array_fill(0, $elementsCount, $this->element); $this->driver ->expects($this->once()) ->method('findElements') diff --git a/tests/Command/Wait/WaitForElementNotVisibleCommandTest.php b/tests/Command/Wait/WaitForElementNotVisibleCommandTest.php index e8acc21d..adcedef6 100644 --- a/tests/Command/Wait/WaitForElementNotVisibleCommandTest.php +++ b/tests/Command/Wait/WaitForElementNotVisibleCommandTest.php @@ -5,7 +5,6 @@ use Facebook\WebDriver\Exception\NoSuchElementException; use Facebook\WebDriver\Exception\StaleElementReferenceException; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverExpectedCondition; use Facebook\WebDriver\WebDriverWait; use Tienvx\Bundle\MbtBundle\Command\Wait\WaitForElementNotVisibleCommand; @@ -27,7 +26,6 @@ protected function createCommand(): WaitForElementNotVisibleCommand */ public function testRun(bool $displayed, bool $present, bool $stale): void { - $element = $this->createMock(WebDriverElement::class); $mock = $this->driver ->expects($this->once()) ->method('findElement') @@ -37,8 +35,8 @@ public function testRun(bool $displayed, bool $present, bool $stale): void && 'title' === $selector->getValue(); })); if ($present) { - $mock->willReturn($element); - $mock = $element + $mock->willReturn($this->element); + $mock = $this->element ->expects($this->once()) ->method('isDisplayed'); if ($stale) { diff --git a/tests/Command/Wait/WaitForElementPresentCommandTest.php b/tests/Command/Wait/WaitForElementPresentCommandTest.php index 189588eb..89d7ea62 100644 --- a/tests/Command/Wait/WaitForElementPresentCommandTest.php +++ b/tests/Command/Wait/WaitForElementPresentCommandTest.php @@ -4,7 +4,6 @@ use Facebook\WebDriver\Exception\NoSuchElementException; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverExpectedCondition; use Facebook\WebDriver\WebDriverWait; use Tienvx\Bundle\MbtBundle\Command\Wait\WaitForElementPresentCommand; @@ -26,7 +25,6 @@ protected function createCommand(): WaitForElementPresentCommand */ public function testRun(bool $present): void { - $element = $this->createMock(WebDriverElement::class); $mock = $this->driver ->expects($this->once()) ->method('findElement') @@ -36,7 +34,7 @@ public function testRun(bool $present): void && 'title' === $selector->getValue(); })); if ($present) { - $mock->willReturn($element); + $mock->willReturn($this->element); } else { $mock->willThrowException(new NoSuchElementException('Element missing')); } diff --git a/tests/Command/Wait/WaitForElementVisibleCommandTest.php b/tests/Command/Wait/WaitForElementVisibleCommandTest.php index c59176b6..90dde3e5 100644 --- a/tests/Command/Wait/WaitForElementVisibleCommandTest.php +++ b/tests/Command/Wait/WaitForElementVisibleCommandTest.php @@ -4,7 +4,6 @@ use Facebook\WebDriver\Exception\StaleElementReferenceException; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverExpectedCondition; use Facebook\WebDriver\WebDriverWait; use Tienvx\Bundle\MbtBundle\Command\Wait\WaitForElementVisibleCommand; @@ -26,7 +25,6 @@ protected function createCommand(): WaitForElementVisibleCommand */ public function testRun(bool $displayed, bool $stale): void { - $element = $this->createMock(WebDriverElement::class); $this->driver ->expects($this->once()) ->method('findElement') @@ -35,8 +33,8 @@ public function testRun(bool $displayed, bool $stale): void && 'id' === $selector->getMechanism() && 'title' === $selector->getValue(); })) - ->willReturn($element); - $mock = $element + ->willReturn($this->element); + $mock = $this->element ->expects($this->once()) ->method('isDisplayed'); if ($stale) { diff --git a/tests/Command/Window/SelectFrameCommandTest.php b/tests/Command/Window/SelectFrameCommandTest.php index ac4244e3..4f3a9fe5 100644 --- a/tests/Command/Window/SelectFrameCommandTest.php +++ b/tests/Command/Window/SelectFrameCommandTest.php @@ -4,7 +4,6 @@ use Facebook\WebDriver\Remote\RemoteTargetLocator; use Facebook\WebDriver\WebDriverBy; -use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverExpectedCondition; use Facebook\WebDriver\WebDriverWait; use Tienvx\Bundle\MbtBundle\Command\Window\SelectFrameCommand; @@ -34,7 +33,6 @@ protected function createCommand(): SelectFrameCommand public function testRun(string $target, string $method, ?array $params): void { if (is_null($params)) { - $element = $this->createMock(WebDriverElement::class); $this->driver ->expects($this->exactly(2)) ->method('findElement') @@ -43,8 +41,8 @@ public function testRun(string $target, string $method, ?array $params): void && 'link text' === $selector->getMechanism() && 'Read More' === $selector->getValue(); })) - ->willReturn($element); - $params = [$element]; + ->willReturn($this->element); + $params = [$this->element]; $wait = $this->createMock(WebDriverWait::class); $wait ->expects($this->once()) diff --git a/tests/EventListener/EntitySubscriberTest.php b/tests/EventListener/EntitySubscriberTest.php index 673fbabb..5e4031d8 100644 --- a/tests/EventListener/EntitySubscriberTest.php +++ b/tests/EventListener/EntitySubscriberTest.php @@ -5,6 +5,7 @@ use Doctrine\ORM\Event\LifecycleEventArgs; use Doctrine\ORM\Events; use Doctrine\Persistence\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\MessageBusInterface; @@ -21,9 +22,16 @@ */ class EntitySubscriberTest extends TestCase { + protected MessageBusInterface|MockObject $messageBus; + + protected function setUp(): void + { + $this->messageBus = $this->createMock(MessageBusInterface::class); + } + public function testGetSubscribedEvents(): void { - $subscriber = new EntitySubscriber($this->createMock(MessageBusInterface::class)); + $subscriber = new EntitySubscriber($this->messageBus); $this->assertSame([ Events::postPersist, ], $subscriber->getSubscribedEvents()); @@ -33,13 +41,12 @@ public function testPostPersistBug(): void { $bug = new Bug(); $bug->setId(23); - $messageBus = $this->createMock(MessageBusInterface::class); - $messageBus + $this->messageBus ->expects($this->once()) ->method('dispatch') ->with($this->callback(fn ($message) => $message instanceof ReduceBugMessage && 23 === $message->getId())) ->willReturn(new Envelope(new \stdClass())); - $subscriber = new EntitySubscriber($messageBus); + $subscriber = new EntitySubscriber($this->messageBus); $args = new LifecycleEventArgs($bug, $this->createMock(ObjectManager::class)); $subscriber->postPersist($args); } diff --git a/tests/MessageHandler/RecordVideoMessageHandlerTest.php b/tests/MessageHandler/RecordVideoMessageHandlerTest.php index ba1c8a78..84418553 100644 --- a/tests/MessageHandler/RecordVideoMessageHandlerTest.php +++ b/tests/MessageHandler/RecordVideoMessageHandlerTest.php @@ -2,6 +2,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\MessageHandler; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Tienvx\Bundle\MbtBundle\Message\RecordVideoMessage; use Tienvx\Bundle\MbtBundle\MessageHandler\RecordVideoMessageHandler; @@ -13,7 +14,7 @@ */ class RecordVideoMessageHandlerTest extends TestCase { - protected BugHelperInterface $bugHelper; + protected BugHelperInterface|MockObject $bugHelper; protected RecordVideoMessageHandler $handler; protected RecordVideoMessage $message; diff --git a/tests/MessageHandler/ReduceBugMessageHandlerTest.php b/tests/MessageHandler/ReduceBugMessageHandlerTest.php index 6d9fe3fa..8d5c6acf 100644 --- a/tests/MessageHandler/ReduceBugMessageHandlerTest.php +++ b/tests/MessageHandler/ReduceBugMessageHandlerTest.php @@ -2,6 +2,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\MessageHandler; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Tienvx\Bundle\MbtBundle\Message\ReduceBugMessage; use Tienvx\Bundle\MbtBundle\MessageHandler\ReduceBugMessageHandler; @@ -13,7 +14,7 @@ */ class ReduceBugMessageHandlerTest extends TestCase { - protected BugHelperInterface $bugHelper; + protected BugHelperInterface|MockObject $bugHelper; protected ReduceBugMessageHandler $handler; protected ReduceBugMessage $message; diff --git a/tests/MessageHandler/ReduceStepsMessageHandlerTest.php b/tests/MessageHandler/ReduceStepsMessageHandlerTest.php index 5eec79c6..3a45a087 100644 --- a/tests/MessageHandler/ReduceStepsMessageHandlerTest.php +++ b/tests/MessageHandler/ReduceStepsMessageHandlerTest.php @@ -2,6 +2,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\MessageHandler; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Tienvx\Bundle\MbtBundle\Message\ReduceStepsMessage; use Tienvx\Bundle\MbtBundle\MessageHandler\ReduceStepsMessageHandler; @@ -13,7 +14,7 @@ */ class ReduceStepsMessageHandlerTest extends TestCase { - protected BugHelperInterface $bugHelper; + protected BugHelperInterface|MockObject $bugHelper; protected ReduceStepsMessageHandler $handler; protected ReduceStepsMessage $message; diff --git a/tests/MessageHandler/ReportBugMessageHandlerTest.php b/tests/MessageHandler/ReportBugMessageHandlerTest.php index a1e89fb3..04facf6a 100644 --- a/tests/MessageHandler/ReportBugMessageHandlerTest.php +++ b/tests/MessageHandler/ReportBugMessageHandlerTest.php @@ -2,6 +2,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\MessageHandler; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Tienvx\Bundle\MbtBundle\Message\ReportBugMessage; use Tienvx\Bundle\MbtBundle\MessageHandler\ReportBugMessageHandler; @@ -13,7 +14,7 @@ */ class ReportBugMessageHandlerTest extends TestCase { - protected BugHelperInterface $bugHelper; + protected BugHelperInterface|MockObject $bugHelper; protected ReportBugMessageHandler $handler; protected ReportBugMessage $message; diff --git a/tests/Reducer/DispatcherTestCase.php b/tests/Reducer/DispatcherTestCase.php index 9629eefe..9be6f841 100644 --- a/tests/Reducer/DispatcherTestCase.php +++ b/tests/Reducer/DispatcherTestCase.php @@ -3,6 +3,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Reducer; use PHPUnit\Framework\Constraint\Callback; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\MessageBusInterface; @@ -15,7 +16,7 @@ abstract class DispatcherTestCase extends TestCase { protected DispatcherInterface $dispatcher; - protected MessageBusInterface $messageBus; + protected MessageBusInterface|MockObject $messageBus; protected BugInterface $bug; protected array $pairs = []; diff --git a/tests/Reducer/Random/RandomReducerTest.php b/tests/Reducer/Random/RandomReducerTest.php index 4d7066db..3b4b2004 100644 --- a/tests/Reducer/Random/RandomReducerTest.php +++ b/tests/Reducer/Random/RandomReducerTest.php @@ -2,6 +2,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Reducer\Random; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Reducer\Random\RandomDispatcher; @@ -18,8 +19,8 @@ */ class RandomReducerTest extends TestCase { - protected RandomDispatcher $dispatcher; - protected RandomHandler $handler; + protected RandomDispatcher|MockObject $dispatcher; + protected RandomHandler|MockObject $handler; protected RandomReducer $reducer; protected function setUp(): void diff --git a/tests/Reducer/Split/SplitReducerTest.php b/tests/Reducer/Split/SplitReducerTest.php index 63fb9e32..a5c92992 100644 --- a/tests/Reducer/Split/SplitReducerTest.php +++ b/tests/Reducer/Split/SplitReducerTest.php @@ -2,6 +2,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Reducer\Split; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Reducer\ReducerManager; @@ -18,8 +19,8 @@ */ class SplitReducerTest extends TestCase { - protected SplitDispatcher $dispatcher; - protected SplitHandler $handler; + protected SplitDispatcher|MockObject $dispatcher; + protected SplitHandler|MockObject $handler; protected SplitReducer $reducer; protected function setUp(): void diff --git a/tests/Repository/BugRepositoryTest.php b/tests/Repository/BugRepositoryTest.php index 9aca76e6..217e095e 100644 --- a/tests/Repository/BugRepositoryTest.php +++ b/tests/Repository/BugRepositoryTest.php @@ -7,6 +7,7 @@ use Doctrine\ORM\Decorator\EntityManagerDecorator; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\Persistence\ManagerRegistry; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Tienvx\Bundle\MbtBundle\Entity\Bug; use Tienvx\Bundle\MbtBundle\Entity\Progress; @@ -25,7 +26,7 @@ */ class BugRepositoryTest extends TestCase { - protected EntityManagerDecorator $manager; + protected EntityManagerDecorator|MockObject $manager; protected BugInterface $bug; protected BugRepositoryInterface $bugRepository; protected Connection $connection; diff --git a/tests/Service/Bug/BugHelperTest.php b/tests/Service/Bug/BugHelperTest.php index 63596ba5..40af8967 100644 --- a/tests/Service/Bug/BugHelperTest.php +++ b/tests/Service/Bug/BugHelperTest.php @@ -3,6 +3,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service\Bug; use Exception; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException; @@ -43,12 +44,12 @@ */ class BugHelperTest extends TestCase { - protected ReducerManagerInterface $reducerManager; - protected BugRepositoryInterface $bugRepository; - protected MessageBusInterface $messageBus; - protected BugNotifierInterface $bugNotifier; - protected BugStepsRunner $stepsRunner; - protected ConfigInterface $config; + protected ReducerManagerInterface|MockObject $reducerManager; + protected BugRepositoryInterface|MockObject $bugRepository; + protected MessageBusInterface|MockObject $messageBus; + protected BugNotifierInterface|MockObject $bugNotifier; + protected BugStepsRunner|MockObject $stepsRunner; + protected ConfigInterface|MockObject $config; protected BugHelperInterface $helper; protected Revision $revision; protected BugInterface $bug; diff --git a/tests/Service/Step/Builder/PetrinetDomainLogicTest.php b/tests/Service/Step/Builder/PetrinetDomainLogicTest.php index 08593ab4..aa273cf5 100644 --- a/tests/Service/Step/Builder/PetrinetDomainLogicTest.php +++ b/tests/Service/Step/Builder/PetrinetDomainLogicTest.php @@ -3,6 +3,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service\Step\Builder; use Petrinet\Model\MarkingInterface; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SingleColorPetrinet\Model\Color; use SingleColorPetrinet\Model\ColorfulMarking; @@ -23,9 +24,9 @@ */ class PetrinetDomainLogicTest extends TestCase { - protected GuardedTransitionServiceInterface $transitionService; - protected MarkingHelperInterface $markingHelper; - protected PetrinetInterface $petrinet; + protected GuardedTransitionServiceInterface|MockObject $transitionService; + protected MarkingHelperInterface|MockObject $markingHelper; + protected PetrinetInterface|MockObject $petrinet; protected PetrinetDomainLogic $petrinetDomainLogic; protected array $transitions; protected array $markings; diff --git a/tests/Service/Step/Runner/StepRunnerTest.php b/tests/Service/Step/Runner/StepRunnerTest.php index bb90febe..342fc16a 100644 --- a/tests/Service/Step/Runner/StepRunnerTest.php +++ b/tests/Service/Step/Runner/StepRunnerTest.php @@ -3,6 +3,7 @@ namespace Tienvx\Bundle\MbtBundle\Tests\Service\Step\Runner; use Facebook\WebDriver\Remote\RemoteWebDriver; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use SingleColorPetrinet\Model\ColorInterface; use Tienvx\Bundle\MbtBundle\Command\CommandManager; @@ -30,9 +31,9 @@ class StepRunnerTest extends TestCase { protected RevisionInterface $revision; protected array $commands = []; - protected CommandManager $commandManager; + protected CommandManager|MockObject $commandManager; protected RemoteWebDriver $driver; - protected ColorInterface $color; + protected ColorInterface|MockObject $color; protected array $valuesInstances = []; protected function setUp(): void From 6c6f998f1b3250c5288b04b542aa7f9cdb94d382 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 26 Jul 2022 22:21:01 +0700 Subject: [PATCH 84/85] Fix missing service arguments --- config/services.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/config/services.php b/config/services.php index dc7d100f..995b92f5 100644 --- a/config/services.php +++ b/config/services.php @@ -19,6 +19,7 @@ use Tienvx\Bundle\MbtBundle\Generator\GeneratorManager; use Tienvx\Bundle\MbtBundle\Generator\GeneratorManagerInterface; use Tienvx\Bundle\MbtBundle\Generator\RandomGenerator; +use Tienvx\Bundle\MbtBundle\MessageHandler\CreateBugMessageHandler; use Tienvx\Bundle\MbtBundle\MessageHandler\RecordVideoMessageHandler; use Tienvx\Bundle\MbtBundle\MessageHandler\ReduceBugMessageHandler; use Tienvx\Bundle\MbtBundle\MessageHandler\ReduceStepsMessageHandler; @@ -111,10 +112,16 @@ service(BugHelperInterface::class), ]) ->autoconfigure(true) - ->set(ReducerManager::class) - ->alias(ReducerManagerInterface::class, ReducerManager::class) + ->set(CreateBugMessageHandler::class) + ->args([ + service(ConfigInterface::class), + service(TaskRepositoryInterface::class), + ]) + ->autoconfigure(true) // Reducers + ->set(ReducerManager::class) + ->alias(ReducerManagerInterface::class, ReducerManager::class) ->set(RandomDispatcher::class) ->args([ service(MessageBusInterface::class), @@ -125,6 +132,7 @@ service(MessageBusInterface::class), service(BugStepsRunner::class), service(StepsBuilderInterface::class), + service(ConfigInterface::class), ]) ->set(RandomReducer::class) ->args([ @@ -142,6 +150,7 @@ service(MessageBusInterface::class), service(BugStepsRunner::class), service(StepsBuilderInterface::class), + service(ConfigInterface::class), ]) ->set(SplitReducer::class) ->args([ @@ -209,6 +218,7 @@ ->args([ service(GeneratorManagerInterface::class), service(TaskRepositoryInterface::class), + service(MessageBusInterface::class), service(ExploreStepsRunner::class), service(ConfigInterface::class), ]) From 71f565e8eb4ac3525acf2baab1902a31f8b6c10e Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 26 Jul 2022 23:05:38 +0700 Subject: [PATCH 85/85] Revert auto configuration --- src/Plugin/PluginInterface.php | 3 --- src/TienvxMbtBundle.php | 4 ++++ tests/TienvxMbtBundleTest.php | 4 ++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Plugin/PluginInterface.php b/src/Plugin/PluginInterface.php index f90dd3fe..46863a4b 100644 --- a/src/Plugin/PluginInterface.php +++ b/src/Plugin/PluginInterface.php @@ -2,9 +2,6 @@ namespace Tienvx\Bundle\MbtBundle\Plugin; -use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; - -#[Autoconfigure(tags: [PluginInterface::TAG], lazy: true)] interface PluginInterface { public const TAG = 'mbt_bundle.plugin'; diff --git a/src/TienvxMbtBundle.php b/src/TienvxMbtBundle.php index 14d2ceae..99c2b638 100644 --- a/src/TienvxMbtBundle.php +++ b/src/TienvxMbtBundle.php @@ -8,6 +8,7 @@ use Symfony\Component\HttpKernel\Bundle\AbstractBundle; use Tienvx\Bundle\MbtBundle\Command\CommandManager; use Tienvx\Bundle\MbtBundle\DependencyInjection\Compiler\PluginPass; +use Tienvx\Bundle\MbtBundle\Plugin\PluginInterface; use Tienvx\Bundle\MbtBundle\Service\SelenoidHelper; class TienvxMbtBundle extends AbstractBundle @@ -37,5 +38,8 @@ public function loadExtension(array $config, ContainerConfigurator $container, C ->get(CommandManager::class) ->call('setUploadDir', [$config[static::UPLOAD_DIR]]) ; + $builder->registerForAutoconfiguration(PluginInterface::class) + ->setLazy(true) + ->addTag(PluginInterface::TAG); } } diff --git a/tests/TienvxMbtBundleTest.php b/tests/TienvxMbtBundleTest.php index 52049106..30b5dfec 100644 --- a/tests/TienvxMbtBundleTest.php +++ b/tests/TienvxMbtBundleTest.php @@ -11,6 +11,7 @@ use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Tienvx\Bundle\MbtBundle\Command\CommandManager; use Tienvx\Bundle\MbtBundle\DependencyInjection\Compiler\PluginPass; +use Tienvx\Bundle\MbtBundle\Plugin\PluginInterface; use Tienvx\Bundle\MbtBundle\Service\SelenoidHelper; use Tienvx\Bundle\MbtBundle\TienvxMbtBundle; @@ -73,5 +74,8 @@ public function testLoadExtension(): void $this->assertSame([ ['setUploadDir', [static::CONFIG[TienvxMbtBundle::UPLOAD_DIR]]], ], $this->builder->findDefinition(CommandManager::class)->getMethodCalls()); + $autoConfigured = $this->builder->getAutoconfiguredInstanceof()[PluginInterface::class]; + $this->assertTrue($autoConfigured->hasTag(PluginInterface::TAG)); + $this->assertTrue($autoConfigured->isLazy()); } }