clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
<?php
namespace Drupal\Tests\state_machine\Functional;
use Drupal\entity_test\Entity\EntityTestWithBundle;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the Views state filter.
*
* @group state_machine
*/
class StateFilterTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'entity_test',
'options',
'state_machine',
'state_machine_test',
'views',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$user = $this->drupalCreateUser(['administer entity_test content']);
$this->drupalLogin($user);
}
/**
* Tests the filter.
*/
public function testFilter() {
$first_entity = EntityTestWithBundle::create([
'type' => 'first',
'name' => 'First',
]);
$first_entity->save();
$second_entity = EntityTestWithBundle::create([
'type' => 'second',
'name' => 'Second',
'field_state' => 'validation',
]);
$second_entity->save();
$this->drupalGet('/state-machine-test');
$this->assertSession()->pageTextContains('First');
$this->assertSession()->pageTextContains('New');
$this->assertSession()->pageTextContains('Second');
$this->assertSession()->pageTextContains('Validation');
// Confirm that the states from both workflows are in the dropdown.
$expected_options = [
'All' => '- Any -',
'new' => 'New',
'validation' => 'Validation',
'fulfillment' => 'Fulfillment',
'completed' => 'Completed',
'canceled' => 'Canceled',
];
$elements = $this->xpath('//select[@name="field_state_value"]/option');
$found_options = [];
foreach ($elements as $element) {
$found_options[$element->getValue()] = $element->getText();
}
$this->assertEquals($expected_options, $found_options);
}
}

View File

@@ -0,0 +1,153 @@
<?php
namespace Drupal\Tests\state_machine\Functional;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Url;
use Drupal\entity_test\Entity\EntityTestWithBundle;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the state transition form.
*
* @group state_machine
*/
class StateTransitionFormTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'entity_test',
'options',
'state_machine',
'state_machine_test',
'views',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$user = $this->drupalCreateUser(['administer entity_test content', 'view test entity']);
$this->drupalLogin($user);
}
/**
* Tests the transition form.
*/
public function testForm() {
$first_entity = EntityTestWithBundle::create([
'type' => 'first',
'name' => 'First',
]);
$first_entity->save();
$second_entity = EntityTestWithBundle::create([
'type' => 'second',
'name' => 'Second',
'field_state' => 'validation',
]);
$second_entity->save();
$this->drupalGet('/state-machine-test');
$this->assertSession()->pageTextContains('First');
$this->assertSession()->pageTextContains('New');
$buttons = $this->xpath('//form[@id="state-machine-transition-form-entity-test-with-bundle-field-state-1"]/div/input');
$this->assertCount(2, $buttons);
$this->assertEquals('Create', $buttons[0]->getValue());
$this->assertEquals('Cancel', $buttons[1]->getValue());
$this->assertSession()->pageTextContains('Second');
$this->assertSession()->pageTextContains('Validation');
$buttons = $this->xpath('//form[@id="state-machine-transition-form-entity-test-with-bundle-field-state-2"]/div/input');
$this->assertCount(1, $buttons);
$this->assertEquals('Validate', $buttons[0]->getValue());
// Click the Validate button.
$buttons[0]->click();
$this->assertSession()->pageTextContains('First');
$this->assertSession()->pageTextContains('New');
$buttons = $this->xpath('//form[@id="state-machine-transition-form-entity-test-with-bundle-field-state-1"]/div/input');
$this->assertCount(2, $buttons);
$this->assertEquals('Create', $buttons[0]->getValue());
$this->assertEquals('Cancel', $buttons[1]->getValue());
$this->assertSession()->buttonExists('Create');
$this->assertSession()->pageTextContains('Second');
$this->assertSession()->pageTextContains('Fulfillment');
$buttons = $this->xpath('//form[@id="state-machine-transition-form-entity-test-with-bundle-field-state-2"]/div/input');
$this->assertCount(1, $buttons);
$this->assertEquals('Fulfill', $buttons[0]->getValue());
}
/**
* Tests the confirmation form.
*/
public function testConfirmationForm() {
$view_display = EntityViewDisplay::create([
'targetEntityType' => 'entity_test_with_bundle',
'bundle' => 'first',
'mode' => 'default',
'status' => TRUE,
]);
$view_display->setComponent('field_state', [
'type' => 'state_transition_form',
'settings' => [
'require_confirmation' => TRUE,
],
]);
$view_display->save();
$first_entity = EntityTestWithBundle::create([
'type' => 'first',
'name' => 'First',
]);
$first_entity->save();
$this->drupalGet($first_entity->toUrl('canonical'));
$this->assertSession()->pageTextContains('First');
$buttons = $this->xpath('//form[@id="state-machine-transition-form-entity-test-with-bundle-field-state-1"]/div/a');
$this->assertCount(2, $buttons);
$this->assertEquals('Create', $buttons[0]->getText());
$this->assertEquals('Cancel', $buttons[1]->getText());
$this->assertSession()->linkExists('Create');
// Click the Create button.
$buttons[0]->click();
$this->assertSession()->pageTextContains('Are you sure you want to apply this transition?');
$this->assertSession()->pageTextContains('From: New');
$this->assertSession()->pageTextContains('To: Fulfillment');
$this->assertSession()->pageTextContains('Transition: Create');
$this->assertSession()->pageTextContains('Test entity with bundle: First');
$this->assertSession()->pageTextContains('This action cannot be undone.');
$this->assertSession()->linkExists('Cancel');
$this->submitForm([], 'Confirm');
$buttons = $this->xpath('//form[@id="state-machine-transition-form-entity-test-with-bundle-field-state-1"]/div/a');
$this->assertCount(1, $buttons);
$this->assertEquals('Fulfill', $buttons[0]->getText());
$transitions = [
'create' => 403,
'cancel' => 403,
'fulfill' => 200,
];
// Ensure the state transition form route doesn't allow access for
// transitions that are not allowed.
foreach ($transitions as $transition_id => $expected_status_code) {
$route = Url::fromRoute('entity.entity_test_with_bundle.state_transition_form', [
'entity_test_with_bundle' => $first_entity->id(),
'field_name' => 'field_state',
'transition_id' => $transition_id,
]);
$this->drupalGet($route);
$this->assertSession()->statusCodeEquals($expected_status_code);
}
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace Drupal\Tests\state_machine\FunctionalJavascript;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\entity_test\Entity\EntityTestWithBundle;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Tests the state transition form.
*
* @group state_machine
*/
class StateTransitionFormTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'entity_test',
'state_machine_test',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$user = $this->drupalCreateUser(['administer entity_test content', 'view test entity']);
$this->drupalLogin($user);
}
/**
* Tests the confirmation form rendered in a modal.
*/
public function testConfirmationFormWithModal() {
$view_display = EntityViewDisplay::create([
'targetEntityType' => 'entity_test_with_bundle',
'bundle' => 'first',
'mode' => 'default',
'status' => TRUE,
]);
$view_display->setComponent('field_state', [
'type' => 'state_transition_form',
'settings' => [
'require_confirmation' => TRUE,
'use_modal' => TRUE,
],
]);
$view_display->save();
$first_entity = EntityTestWithBundle::create([
'type' => 'first',
'name' => 'First',
]);
$first_entity->save();
$this->drupalGet($first_entity->toUrl('canonical'));
$this->assertSession()->pageTextContains('First');
$buttons = $this->xpath('//form[@id="state-machine-transition-form-entity-test-with-bundle-field-state-1"]/div/a');
$this->assertCount(2, $buttons);
$this->assertEquals('Create', $buttons[0]->getText());
$this->assertEquals('Cancel', $buttons[1]->getText());
// Click the Create link.
$buttons[0]->click();
$this->assertSession()->assertWaitOnAjaxRequest();
$this->assertSession()->pageTextContains('Are you sure you want to apply this transition?');
$this->assertSession()->pageTextContains('From: New');
$this->assertSession()->pageTextContains('To: Fulfillment');
$this->assertSession()->pageTextContains('Transition: Create');
$this->assertSession()->pageTextContains('Test entity with bundle: First');
$this->assertSession()->pageTextContains('This action cannot be undone.');
$this->assertSession()->linkExists('Cancel');
$this->assertSession()->buttonExists('Confirm');
}
}

View File

@@ -0,0 +1,196 @@
<?php
namespace Drupal\Tests\state_machine\Kernel;
use Drupal\entity_test\Entity\EntityTestWithBundle;
use Drupal\Tests\field\Kernel\FieldKernelTestBase;
/**
* @coversDefaultClass \Drupal\state_machine\Plugin\Field\FieldType\StateItem
* @group state_machine
*/
class StateItemTest extends FieldKernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'entity_test',
'field',
'user',
'state_machine',
'state_machine_test',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('entity_test_with_bundle');
$this->installConfig(['state_machine_test']);
}
/**
* @covers ::applyTransitionById
*/
public function testInvalidTransitionApply() {
$entity = EntityTestWithBundle::create([
'name' => 'first',
'type' => 'first',
]);
/** @var \Drupal\state_machine\Plugin\Field\FieldType\StateItemInterface $state_item */
$state_item = $entity->get('field_state')->first();
$this->expectException(\InvalidArgumentException::class);
$state_item->applyTransitionById('INVALID');
}
/**
* @dataProvider providerTestField
*/
public function testField($initial_state, $allowed_transitions, $invalid_new_state, $valid_transition, $expected_new_state) {
$entity = EntityTestWithBundle::create([
'name' => 'second',
'type' => 'second',
'field_state' => $initial_state,
]);
$this->assertEquals($initial_state, $entity->get('field_state')->value);
/** @var \Drupal\state_machine\Plugin\Field\FieldType\StateItemInterface $state_item */
$state_item = $entity->get('field_state')->first();
// Confirm that the transitions are correct.
$transitions = $state_item->getTransitions();
$this->assertCount(count($allowed_transitions), $transitions);
$this->assertEquals($allowed_transitions, array_keys($transitions));
if (count($allowed_transitions) > 0) {
foreach ($allowed_transitions as $transition_id) {
$this->assertTrue($state_item->isTransitionAllowed($transition_id));
}
}
$this->assertFalse($state_item->isTransitionAllowed('foo'));
// Confirm that invalid states are recognized.
if ($invalid_new_state) {
$state_item->value = $invalid_new_state;
$this->assertEquals($initial_state, $state_item->getOriginalId());
$this->assertEquals($invalid_new_state, $state_item->getId());
$this->assertFalse($state_item->isValid());
}
// Revert to the initial state because the valid transaction could be
// invalid from the invalid_new_state.
$state_item->value = $initial_state;
// Retrieve all workflow transitions.
$workflow = $state_item->getWorkflow();
$all_transitions = $workflow->getTransitions();
// Pick a random invalid transition and assert it throws an Exception.
$invalid_transitions = array_diff_key($all_transitions, $transitions);
if ($invalid_transitions) {
$random_key = array_rand($invalid_transitions);
$this->expectException(\InvalidArgumentException::class);
$state_item->applyTransition($invalid_transitions[$random_key]);
// Also try applying by ID.
$this->expectException(\InvalidArgumentException::class);
$state_item->applyTransitionById($random_key);
}
$state_item->applyTransitionById($valid_transition);
$this->assertEquals($initial_state, $state_item->getOriginalId());
$this->assertEquals($expected_new_state, $state_item->getId());
$this->assertTrue($state_item->isValid());
}
/**
* Data provider for ::testField.
*
* @return array
* A list of testField function arguments.
*/
public static function providerTestField() {
$data = [];
$data['new->validation'] = ['new', ['create', 'cancel'], 'fulfillment', 'create', 'validation'];
$data['new->canceled'] = ['new', ['create', 'cancel'], 'completed', 'cancel', 'canceled'];
// The workflow defines validation->fulfillment and validation->canceled
// transitions, but the second one is forbidden by the GenericGuard.
$data['validation->fulfillment'] = ['validation', ['validate'], 'completed', 'validate', 'fulfillment'];
// The workflow defines fulfillment->completed and fulfillment->canceled
// transitions, but the second one is forbidden by the FulfillmentGuard.
$data['fulfillment->completed'] = ['fulfillment', ['fulfill'], 'new', 'fulfill', 'completed'];
return $data;
}
/**
* @dataProvider providerSettableOptions
*/
public function testSettableOptions($initial_state, $available_options) {
$entity = EntityTestWithBundle::create([
'name' => 'second',
'type' => 'second',
'field_state' => $initial_state,
]);
$this->assertEquals($initial_state, $entity->get('field_state')->value);
// An invalid state should not have any settable options.
$this->assertEquals($available_options, $entity->get('field_state')->first()->getSettableOptions());
}
/**
* Data provider for ::providerSettableOptions.
*
* @return array
* A list of providerSettableOptions function arguments.
*/
public static function providerSettableOptions() {
$data = [];
$data['new'] = ['new', ['canceled' => 'Canceled', 'validation' => 'Validation', 'new' => 'New']];
$data['invalid'] = ['invalid', []];
return $data;
}
/**
* @covers ::generateSampleValue
*/
public function testGenerateSampleValue() {
$entity = EntityTestWithBundle::create([
'name' => 'first',
'type' => 'first',
'field_state' => NULL,
]);
$entity->field_state->generateSampleItems();
/** @var \Drupal\state_machine\Plugin\Field\FieldType\StateItemInterface $state_item */
$state_item = $entity->get('field_state')->first();
$this->assertEquals('default', $state_item->getWorkflow()->getId());
$this->assertNotEmpty($state_item->getId());
$this->assertTrue(in_array($state_item->getId(), array_keys($state_item->getWorkflow()->getStates())));
$this->entityValidateAndSave($entity);
$entity = EntityTestWithBundle::create([
'name' => 'second',
'type' => 'second',
'field_state' => NULL,
]);
$entity->field_state->generateSampleItems();
/** @var \Drupal\state_machine\Plugin\Field\FieldType\StateItemInterface $state_item */
$state_item = $entity->get('field_state')->first();
$this->assertNotEmpty($state_item->getId());
$this->assertTrue(in_array($state_item->getId(), array_keys($state_item->getWorkflow()->getStates())));
$this->entityValidateAndSave($entity);
$entity = EntityTestWithBundle::create([
'name' => 'third',
'type' => 'third',
'field_state' => NULL,
]);
$entity->field_state->generateSampleItems();
/** @var \Drupal\state_machine\Plugin\Field\FieldType\StateItemInterface $state_item */
$state_item = $entity->get('field_state')->first();
$this->assertEquals('two_transitions', $state_item->getWorkflow()->getId());
$this->assertNotEmpty($state_item->getId());
$this->assertTrue(in_array($state_item->getId(), array_keys($state_item->getWorkflow()->getStates())));
$this->entityValidateAndSave($entity);
}
}

View File

@@ -0,0 +1,155 @@
<?php
namespace Drupal\Tests\state_machine\Kernel;
use Drupal\entity_test\Entity\EntityTestWithBundle;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the TransitionEvent.
*
* @group state_machine
*/
class WorkflowTransitionEventTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'entity_test',
'field',
'user',
'state_machine',
'state_machine_test',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('entity_test_with_bundle');
$this->installConfig(['state_machine_test']);
}
/**
* Tests the transition event.
*/
public function testTransitionEvent() {
$entity = EntityTestWithBundle::create([
'type' => 'first',
'name' => 'Test entity',
'field_state' => 'new',
]);
$entity->save();
$entity->get('field_state')->first()->applyTransitionById('create');
$entity->save();
$messages = \Drupal::messenger()->all();
$message = reset($messages);
$this->assertCount(6, $message);
$this->assertEquals('Test entity (field_state) - Fulfillment at pre-transition (workflow: default, transition: create).', (string) $message[0]);
$this->assertEquals('Test entity (field_state) - Fulfillment at group pre-transition (workflow: default, transition: create).', (string) $message[1]);
$this->assertEquals('Test entity (field_state) - Fulfillment at generic pre-transition (workflow: default, transition: create).', (string) $message[2]);
$this->assertEquals('Test entity (field_state) - Fulfillment at post-transition (workflow: default, transition: create).', (string) $message[3]);
$this->assertEquals('Test entity (field_state) - Fulfillment at group post-transition (workflow: default, transition: create).', (string) $message[4]);
$this->assertEquals('Test entity (field_state) - Fulfillment at generic post-transition (workflow: default, transition: create).', (string) $message[5]);
\Drupal::messenger()->deleteAll();
$entity->get('field_state')->first()->applyTransitionById('fulfill');
$entity->save();
$messages = \Drupal::messenger()->all();
$message = reset($messages);
$this->assertCount(4, $message);
$this->assertEquals('Test entity (field_state) - Completed at group pre-transition (workflow: default, transition: fulfill).', (string) $message[0]);
$this->assertEquals('Test entity (field_state) - Completed at generic pre-transition (workflow: default, transition: fulfill).', (string) $message[1]);
$this->assertEquals('Test entity (field_state) - Completed at group post-transition (workflow: default, transition: fulfill).', (string) $message[2]);
$this->assertEquals('Test entity (field_state) - Completed at generic post-transition (workflow: default, transition: fulfill).', (string) $message[3]);
\Drupal::messenger()->deleteAll();
$entity = EntityTestWithBundle::create([
'type' => 'first',
'name' => 'Test entity 2',
'field_state' => 'new',
]);
$entity->save();
$entity->get('field_state')->first()->applyTransitionById('create');
$entity->save();
$messages = \Drupal::messenger()->all();
$message = reset($messages);
$this->assertCount(6, $message);
$this->assertEquals('Test entity 2 (field_state) - Fulfillment at pre-transition (workflow: default, transition: create).', (string) $message[0]);
$this->assertEquals('Test entity 2 (field_state) - Fulfillment at group pre-transition (workflow: default, transition: create).', (string) $message[1]);
$this->assertEquals('Test entity 2 (field_state) - Fulfillment at generic pre-transition (workflow: default, transition: create).', (string) $message[2]);
$this->assertEquals('Test entity 2 (field_state) - Fulfillment at post-transition (workflow: default, transition: create).', (string) $message[3]);
$this->assertEquals('Test entity 2 (field_state) - Fulfillment at group post-transition (workflow: default, transition: create).', (string) $message[4]);
$this->assertEquals('Test entity 2 (field_state) - Fulfillment at generic post-transition (workflow: default, transition: create).', (string) $message[5]);
\Drupal::messenger()->deleteAll();
// Ensure manually setting the state to "create", triggers the cancel
// transition and not the 'fulfill' transition previously applied.
$entity->set('field_state', 'canceled');
$entity->save();
$messages = \Drupal::messenger()->all();
$message = reset($messages);
$this->assertCount(4, $message);
$this->assertEquals('Test entity 2 (field_state) - Canceled at group pre-transition (workflow: default, transition: cancel).', (string) $message[0]);
$this->assertEquals('Test entity 2 (field_state) - Canceled at generic pre-transition (workflow: default, transition: cancel).', (string) $message[1]);
$this->assertEquals('Test entity 2 (field_state) - Canceled at group post-transition (workflow: default, transition: cancel).', (string) $message[2]);
$this->assertEquals('Test entity 2 (field_state) - Canceled at generic post-transition (workflow: default, transition: cancel).', (string) $message[3]);
}
/**
* Tests the transition event with two identical to and from states.
*/
public function testTransitionEventTwoTransitions() {
$entity = EntityTestWithBundle::create([
'type' => 'third',
'name' => 'Test entity',
'field_state' => 'new',
]);
$entity->save();
// Test that applying the second transition also throws the second transition's event.
$entity->get('field_state')->first()->applyTransitionById('complete2');
$entity->save();
$messages = \Drupal::messenger()->all();
$message = reset($messages);
$this->assertCount(4, $message);
$this->assertEquals('Test entity (field_state) - Completed at group pre-transition (workflow: two_transitions, transition: complete2).', (string) $message[0]);
$this->assertEquals('Test entity (field_state) - Completed at generic pre-transition (workflow: two_transitions, transition: complete2).', (string) $message[1]);
$this->assertEquals('Test entity (field_state) - Completed at group post-transition (workflow: two_transitions, transition: complete2).', (string) $message[2]);
$this->assertEquals('Test entity (field_state) - Completed at generic post-transition (workflow: two_transitions, transition: complete2).', (string) $message[3]);
}
/**
* Tests the transition event when a state transition occurs to same value.
*/
public function testTransitionEventSameState() {
$entity = EntityTestWithBundle::create([
'type' => 'fourth',
'name' => 'Test entity',
'field_state' => 'new',
]);
$entity->save();
// Test that explicitly specifying the transition to same fires event.
$entity->get('field_state')->first()->applyTransitionById('same');
$entity->save();
$messages = \Drupal::messenger()->all();
$this->assertCount(4, $messages['status']);
\Drupal::messenger()->deleteAll();
// Test that simply setting the value to same as existing does not dispatch.
$entity->get('field_state')->first()->setValue('new');
$entity->save();
$this->assertEmpty(\Drupal::messenger()->all());
}
}

View File

@@ -0,0 +1,246 @@
<?php
namespace Drupal\Tests\state_machine\Unit\Plugin\Workflow;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\state_machine\Guard\GuardFactoryInterface;
use Drupal\state_machine\Guard\GuardInterface;
use Drupal\state_machine\Plugin\Workflow\Workflow;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
/**
* @coversDefaultClass \Drupal\state_machine\Plugin\Workflow\Workflow
* @group state_machine
*/
class WorkflowTest extends UnitTestCase {
/**
* @covers ::getId
*/
public function testGetId() {
$guard_factory = $this->prophesize(GuardFactoryInterface::class);
$plugin_definition = [
'id' => 'test id',
'label' => 'test label',
'states' => [],
'transitions' => [],
];
$workflow = new Workflow([], 'test', $plugin_definition, $guard_factory->reveal());
$this->assertEquals('test id', $workflow->getId());
}
/**
* @covers ::getLabel
*/
public function testGetLabel() {
$guard_factory = $this->prophesize(GuardFactoryInterface::class);
$plugin_definition = [
'states' => [],
'transitions' => [],
'label' => 'test label',
];
$workflow = new Workflow([], 'test', $plugin_definition, $guard_factory->reveal());
$this->assertEquals('test label', $workflow->getLabel());
}
/**
* @covers ::getStates
* @covers ::getState
*/
public function testGetStates() {
$guard_factory = $this->prophesize(GuardFactoryInterface::class);
$translation = $this->prophesize(TranslationInterface::class);
$translation->translateString(Argument::type(TranslatableMarkup::class))
->willReturn('Draft');
$plugin_definition = [
'states' => [
'draft' => [
'label' => 'Draft',
],
],
'transitions' => [],
];
$workflow = new Workflow([], 'test', $plugin_definition, $guard_factory->reveal());
$workflow->setStringTranslation($translation->reveal());
$state = $workflow->getState('draft');
$this->assertEquals('draft', $state->getId());
$state->setStringTranslation($translation->reveal());
$this->assertEquals('Draft', $state->getLabel());
$this->assertEquals(['draft' => $state], $workflow->getStates());
}
/**
* @covers ::getTransitions
* @covers ::getTransition
*/
public function testGetTransitions() {
$translation = $this->prophesize(TranslationInterface::class);
$translation->translateString(Argument::type(TranslatableMarkup::class))
->willReturn('Publish');
$guard_factory = $this->prophesize(GuardFactoryInterface::class);
$plugin_definition = [
'states' => [
'draft' => [
'label' => 'Draft',
],
'published' => [
'label' => 'Published',
],
],
'transitions' => [
'publish' => [
'label' => 'Publish',
'from' => ['draft'],
'to' => 'published',
],
],
];
$workflow = new Workflow([], 'test', $plugin_definition, $guard_factory->reveal());
$transition = $workflow->getTransition('publish');
$transition->setStringTranslation($translation->reveal());
$this->assertEquals('publish', $transition->getId());
$this->assertEquals('Publish', $transition->getLabel());
$this->assertEquals(['draft' => $workflow->getState('draft')], $transition->getFromStates());
$this->assertEquals($workflow->getState('published'), $transition->getToState());
$this->assertEquals(['publish' => $transition], $workflow->getTransitions());
}
/**
* @covers ::getPossibleTransitions
*/
public function testGetPossibleTransitions() {
$guard_factory = $this->prophesize(GuardFactoryInterface::class);
$plugin_definition = [
'states' => [
'draft' => [
'label' => 'Draft',
],
'review' => [
'label' => 'Review',
],
'published' => [
'label' => 'Published',
],
],
'transitions' => [
'send_to_review' => [
'label' => 'Send to review',
'from' => ['draft'],
'to' => 'review',
],
'publish' => [
'label' => 'Publish',
'from' => ['review'],
'to' => 'published',
],
],
];
$workflow = new Workflow([], 'test', $plugin_definition, $guard_factory->reveal());
$transition = $workflow->getTransition('send_to_review');
$this->assertEquals(['send_to_review' => $transition], $workflow->getPossibleTransitions('draft'));
$transition = $workflow->getTransition('publish');
$this->assertEquals(['publish' => $transition], $workflow->getPossibleTransitions('review'));
$this->assertEquals([], $workflow->getPossibleTransitions('published'));
// Passing an empty state should return all transitions.
$this->assertEquals($workflow->getTransitions(), $workflow->getPossibleTransitions(''));
}
/**
* @covers ::getAllowedTransitions
*/
public function testGetAllowedTransitions() {
$plugin_definition = [
'states' => [
'draft' => [
'label' => 'Draft',
],
'review' => [
'label' => 'Review',
],
'published' => [
'label' => 'Published',
],
],
'transitions' => [
'send_to_review' => [
'label' => 'Send to review',
'from' => ['draft'],
'to' => 'review',
],
'publish' => [
'label' => 'Publish',
'from' => ['review'],
'to' => 'published',
],
],
'group' => 'default',
];
$guard_allow = $this->prophesize(GuardInterface::class);
$guard_allow
->allowed(Argument::cetera())
->willReturn(TRUE);
$guard_deny_publish = $this->prophesize(GuardInterface::class);
$guard_deny_publish
->allowed(Argument::cetera())
->will(function ($args) {
// Allow only the send_to_review transition.
return $args[0]->getId() == 'send_to_review';
});
$guard_factory = $this->prophesize(GuardFactoryInterface::class);
$guard_factory
->get('default')
->willReturn([$guard_allow->reveal(), $guard_deny_publish->reveal()]);
$workflow = new Workflow([], 'test', $plugin_definition, $guard_factory->reveal());
$entity = $this->prophesize(EntityInterface::class)->reveal();
$transition = $workflow->getTransition('send_to_review');
$this->assertEquals(['send_to_review' => $transition], $workflow->getAllowedTransitions('draft', $entity));
$this->assertEquals([], $workflow->getAllowedTransitions('review', $entity));
}
/**
* @covers ::findTransition
*/
public function testFindTransition() {
$guard_factory = $this->prophesize(GuardFactoryInterface::class);
$plugin_definition = [
'states' => [
'draft' => [
'label' => 'Draft',
],
'review' => [
'label' => 'Review',
],
'published' => [
'label' => 'Published',
],
],
'transitions' => [
'send_to_review' => [
'label' => 'Send to review',
'from' => ['draft'],
'to' => 'review',
],
'publish' => [
'label' => 'Publish',
'from' => ['review'],
'to' => 'published',
],
],
];
$workflow = new Workflow([], 'test', $plugin_definition, $guard_factory->reveal());
$transition = $workflow->getTransition('send_to_review');
$this->assertEquals($transition, $workflow->findTransition('draft', 'review'));
$this->assertNull($workflow->findTransition('foo', 'bar'));
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Drupal\Tests\state_machine\Unit\Plugin\WorkflowGroup;
use Drupal\state_machine\Plugin\Workflow\Workflow;
use Drupal\state_machine\Plugin\WorkflowGroup\WorkflowGroup;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\state_machine\Plugin\WorkflowGroup\WorkflowGroup
* @group state_machine
*/
class WorkflowGroupTest extends UnitTestCase {
/**
* The workflow group.
*
* @var \Drupal\state_machine\Plugin\WorkflowGroup\WorkflowGroup
*/
protected $workflowGroup;
/**
* The plugin definition array.
*
* @var array
*/
protected $definition = [
'id' => 'entity_test',
'label' => 'Entity Test',
'entity_type' => 'entity_test',
'class' => WorkflowGroup::class,
'workflow_class' => Workflow::class,
'provider' => 'state_machine_test',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->workflowGroup = new WorkflowGroup([], 'order', $this->definition);
}
/**
* @covers ::getId
*/
public function testGetId() {
$this->assertEquals($this->definition['id'], $this->workflowGroup->getId());
}
/**
* @covers ::getLabel
*/
public function testGetLabel() {
$this->assertEquals($this->definition['label'], $this->workflowGroup->getLabel(), 'Workflow group label matches the expected one');
}
/**
* @covers ::getEntityTypeId
*/
public function testGetEntityTypeId() {
$this->assertEquals($this->definition['entity_type'], $this->workflowGroup->getEntityTypeId(), 'Workflow group entity type id matches the expected one');
}
/**
* @covers ::getWorkflowClass
*/
public function testGetWorkflowClass() {
$this->assertEquals($this->definition['workflow_class'], $this->workflowGroup->getWorkflowClass(), 'Workflow group class matches the expected one');
}
}

View File

@@ -0,0 +1,155 @@
<?php
namespace Drupal\Tests\state_machine\Unit;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\state_machine\Guard\GuardFactoryInterface;
use Drupal\state_machine\Plugin\Workflow\Workflow;
use Drupal\state_machine\Plugin\Workflow\WorkflowInterface;
use Drupal\state_machine\WorkflowGroupManagerInterface;
use Drupal\state_machine\WorkflowManager;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Tests the cache of the workflow manager.
*
* @coversDefaultClass \Drupal\state_machine\WorkflowManager
* @group state_machine
*/
class WorkflowManagerCacheTest extends UnitTestCase {
/**
* An instance of the WorkflowManager. This is the system under test.
*
* @var \Drupal\state_machine\WorkflowManager
*/
protected $workflowManager;
/**
* A mocked instance of the dependency injection container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface|\Prophecy\Prophecy\ObjectProphecy
*/
protected $container;
/**
* A mocked instance of the module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface|\Prophecy\Prophecy\ObjectProphecy
*/
protected $moduleHandler;
/**
* A mocked instance of a cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface|\Prophecy\Prophecy\ObjectProphecy
*/
protected $cacheBackend;
/**
* A mocked instance of the workflow group manager.
*
* @var \Drupal\state_machine\WorkflowGroupManagerInterface|\Prophecy\Prophecy\ObjectProphecy
*/
protected $workflowGroupManager;
/**
* The ID of a mocked workflow plugin used in the test.
*
* @var string
*/
protected $pluginId = 'test_workflow';
/**
* The ID of a mocked workflow group used in the test.
*
* @var string
*/
protected $groupId = 'test_group';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->container = $this->prophesize(ContainerInterface::class);
\Drupal::setContainer($this->container->reveal());
// Mock the dependencies to inject into the workflow manager.
$this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class);
$this->cacheBackend = $this->prophesize(CacheBackendInterface::class);
$this->workflowGroupManager = $this->prophesize(WorkflowGroupManagerInterface::class);
// Instantiate the workflow manager. This is the system under test.
$this->workflowManager = new WorkflowManager(
$this->moduleHandler->reveal(),
$this->cacheBackend->reveal(),
$this->workflowGroupManager->reveal()
);
}
/**
* Tests that workflow plugins are cached upon creation.
*
* @covers ::createInstance
*/
public function testCreateInstance() {
// In this test we will ask the workflow manager repeatedly for an instance
// of a single workflow plugin. Since the manager is supposed to cache the
// plugins, it is expected that the GuardFactory (which is a dependency of
// the Workflow plugin) is not retrieved more than once from the container.
$guard_factory = $this->prophesize(GuardFactoryInterface::class);
$this->container->get('state_machine.guard_factory')
->willReturn($guard_factory->reveal())
->shouldBeCalledOnce();
// It is expected that the workflow manager will retrieve the complete list
// of workflows from the cache backend in order to look up the definition of
// the workflow plugin that we are creating. Even though we are creating
// multiple instances, this should only be called once since the result
// should be cached in memory.
$this->cacheBackend->get('workflow')
->willReturn((object) [
'data' => $this->getMockWorkflowDefinitions(),
])
->shouldBeCalledOnce();
// Once it found the workflow definition, it is expected that the workflow
// group manager will be asked for the definition of the workflow plugin
// that is used by the group. The workflow manager requires this data in
// order to discover the class name of the workflow plugin so that it can be
// instantiated. This too should be called only once.
$this->workflowGroupManager->getDefinition($this->groupId)
->willReturn([
'workflow_class' => Workflow::class,
])
->shouldBeCalledOnce();
// Request the same plugin instance multiple times from the workflow
// manager. The first instance is cached, and all subsequent invocations
// will retrieve the instance from cache.
for ($i = 0; $i < 5; $i++) {
$plugin = $this->workflowManager->createInstance($this->pluginId);
$this->assertInstanceOf(WorkflowInterface::class, $plugin);
}
}
/**
* Gets a mocked workflow plugin definition.
*
* @return array
* The mocked workflow definition.
*/
protected function getMockWorkflowDefinitions() {
return [
'test_workflow' => [
'group' => $this->groupId,
'states' => [],
'transitions' => [],
],
];
}
}