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,6 @@
langcode: en
status: true
dependencies: { }
id: first
label: First
description: ''

View File

@@ -0,0 +1,6 @@
langcode: en
status: true
dependencies: { }
id: fourth
label: Fourth
description: ''

View File

@@ -0,0 +1,6 @@
langcode: en
status: true
dependencies: { }
id: second
label: Second
description: ''

View File

@@ -0,0 +1,6 @@
langcode: en
status: true
dependencies: { }
id: third
label: Third
description: ''

View File

@@ -0,0 +1,21 @@
langcode: en
status: true
dependencies:
config:
- entity_test.entity_test_bundle.first
- field.storage.entity_test_with_bundle.field_state
module:
- state_machine
id: entity_test_with_bundle.first.field_state
field_name: field_state
entity_type: entity_test_with_bundle
bundle: first
label: State
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
workflow: default
field_type: state

View File

@@ -0,0 +1,21 @@
langcode: en
status: true
dependencies:
config:
- entity_test.entity_test_bundle.fourth
- field.storage.entity_test_with_bundle.field_state
module:
- state_machine
id: entity_test_with_bundle.fourth.field_state
field_name: field_state
entity_type: entity_test_with_bundle
bundle: fourth
label: State
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
workflow: same_transition
field_type: state

View File

@@ -0,0 +1,21 @@
langcode: en
status: true
dependencies:
config:
- entity_test.entity_test_bundle.second
- field.storage.entity_test_with_bundle.field_state
module:
- state_machine
id: entity_test_with_bundle.second.field_state
field_name: field_state
entity_type: entity_test_with_bundle
bundle: second
label: State
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
workflow_callback: 'state_machine_test_workflow_callback'
field_type: state

View File

@@ -0,0 +1,21 @@
langcode: en
status: true
dependencies:
config:
- entity_test.entity_test_bundle.third
- field.storage.entity_test_with_bundle.field_state
module:
- state_machine
id: entity_test_with_bundle.third.field_state
field_name: field_state
entity_type: entity_test_with_bundle
bundle: third
label: State
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings:
workflow: two_transitions
field_type: state

View File

@@ -0,0 +1,18 @@
langcode: en
status: true
dependencies:
module:
- state_machine
- entity_test
id: entity_test_with_bundle.field_state
field_name: field_state
entity_type: entity_test_with_bundle
type: state
settings: { }
module: state_machine
locked: false
cardinality: 1
translatable: false
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@@ -0,0 +1,334 @@
langcode: en
status: true
dependencies:
config:
- field.storage.entity_test_with_bundle.field_state
module:
- entity_test
- options
- state_machine
id: state_machine_test
label: 'State Machine Test'
module: views
description: ''
tag: ''
base_table: entity_test_with_bundle_field_data
base_field: id
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: none
options: { }
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: mini
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous:
next:
style:
type: table
row:
type: fields
fields:
name:
table: entity_test_with_bundle_field_data
field: name
id: name
entity_type: null
entity_field: name
plugin_id: field
relationship: none
group_type: group
admin_label: ''
label: Name
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: string
settings: { }
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
field_state:
id: field_state
table: entity_test_with_bundle__field_state
field: field_state
relationship: none
group_type: group
admin_label: ''
label: State
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: list_default
settings: { }
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
plugin_id: field
field_state_1:
id: field_state_1
table: entity_test_with_bundle__field_state
field: field_state
relationship: none
group_type: group
admin_label: ''
label: Actions
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: state_transition_form
settings: { }
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
plugin_id: field
filters:
field_state_value:
id: field_state_value
table: entity_test_with_bundle__field_state
field: field_state_value
relationship: none
group_type: group
admin_label: ''
operator: in
value: { }
group: 1
exposed: true
expose:
operator_id: field_state_value_op
label: State
description: ''
use_operator: false
operator: field_state_value_op
identifier: field_state_value
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
anonymous: '0'
administrator: '0'
merchant: '0'
reduce: false
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
plugin_id: state_machine_state
sorts: { }
title: 'State Machine Test'
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- url.query_args
tags:
- 'config:field.storage.entity_test_with_bundle.field_state'
page_1:
display_plugin: page
id: page_1
display_title: Page
position: 1
display_options:
display_extenders: { }
path: state-machine-test
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- url.query_args
tags:
- 'config:field.storage.entity_test_with_bundle.field_state'

View File

@@ -0,0 +1,123 @@
<?php
namespace Drupal\state_machine_test\EventSubscriber;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\state_machine\Event\WorkflowTransitionEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class WorkflowTransitionEventSubscriber implements EventSubscriberInterface {
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Constructs a new WorkflowTransitionEventSubscriber object.
*
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(MessengerInterface $messenger) {
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
$events = [
'entity_test_with_bundle.create.pre_transition' => 'onPreTransition',
'entity_test_with_bundle.create.post_transition' => 'onPostTransition',
'entity_test_with_bundle.pre_transition' => 'onGroupPreTransition',
'entity_test_with_bundle.post_transition' => 'onGroupPostTransition',
'state_machine.pre_transition' => 'onGenericPreTransition',
'state_machine.post_transition' => 'onGenericPostTransition',
];
return $events;
}
/**
* Reacts to the 'entity_test_with_bundle.create.pre_transition' event.
*
* @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event
* The transition event.
*/
public function onPreTransition(WorkflowTransitionEvent $event) {
$this->setMessage($event, 'pre-transition');
}
/**
* Reacts to the 'entity_test_with_bundle.create.post_transition' event.
*
* @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event
* The transition event.
*/
public function onPostTransition(WorkflowTransitionEvent $event) {
$this->setMessage($event, 'post-transition');
}
/**
* Reacts to the 'entity_test_with_bundle.pre_transition' event.
*
* @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event
* The transition event.
*/
public function onGroupPreTransition(WorkflowTransitionEvent $event) {
$this->setMessage($event, 'group pre-transition');
}
/**
* Reacts to the 'entity_test_with_bundle.post_transition' event.
*
* @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event
* The transition event.
*/
public function onGroupPostTransition(WorkflowTransitionEvent $event) {
$this->setMessage($event, 'group post-transition');
}
/**
* Reacts to the 'state_machine.pre_transition' event.
*
* @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event
* The transition event.
*/
public function onGenericPreTransition(WorkflowTransitionEvent $event) {
$this->setMessage($event, 'generic pre-transition');
}
/**
* Reacts to the 'state_machine.post_transition' event.
*
* @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event
* The transition event.
*/
public function onGenericPostTransition(WorkflowTransitionEvent $event) {
$this->setMessage($event, 'generic post-transition');
}
/**
* Sets a message with event information for test purposes.
*
* @param \Drupal\state_machine\Event\WorkflowTransitionEvent $event
* The transition event.
* @param string $phase
* The phase during which the event occurred.
*/
protected function setMessage(WorkflowTransitionEvent $event, $phase) {
$this->messenger->addMessage(new TranslatableMarkup('@entity_label (@field_name) - @state_label at @phase (workflow: @workflow, transition: @transition).', [
'@entity_label' => (string) $event->getEntity()->label(),
'@field_name' => $event->getFieldName(),
'@state_label' => $event->getTransition()->getToState()->getLabel(),
'@workflow' => $event->getWorkflow()->getId(),
'@transition' => $event->getTransition()->getId(),
'@phase' => $phase,
]));
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Drupal\state_machine_test\Guard;
use Drupal\Core\Entity\EntityInterface;
use Drupal\state_machine\Guard\GuardInterface;
use Drupal\state_machine\Plugin\Workflow\WorkflowInterface;
use Drupal\state_machine\Plugin\Workflow\WorkflowTransition;
class FulfillmentGuard implements GuardInterface {
/**
* {@inheritdoc}
*/
public function allowed(WorkflowTransition $transition, WorkflowInterface $workflow, EntityInterface $entity) {
// Don't allow entities in fulfillment to be cancelled.
if ($transition->getId() == 'cancel' && $entity->field_state->first()->value == 'fulfillment') {
return FALSE;
}
return TRUE;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Drupal\state_machine_test\Guard;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\state_machine\Guard\GuardInterface;
use Drupal\state_machine\Plugin\Workflow\WorkflowInterface;
use Drupal\state_machine\Plugin\Workflow\WorkflowTransition;
class GenericGuard implements GuardInterface {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a new GenericGuard object.
*
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*/
public function __construct(AccountInterface $current_user) {
$this->currentUser = $current_user;
}
/**
* {@inheritdoc}
*/
public function allowed(WorkflowTransition $transition, WorkflowInterface $workflow, EntityInterface $entity) {
// Only a "supervisor" user can cancel an entity in validation.
if ($transition->getId() == 'cancel' && $entity->field_state->first()->value == 'validation') {
return (bool) array_intersect(['merchant'], $this->currentUser->getRoles());
}
return TRUE;
}
}

View File

@@ -0,0 +1,11 @@
name: State Machine Test
type: module
package: Testing
dependencies:
- drupal:entity_test
- state_machine:state_machine
# Information added by Drupal.org packaging script on 2024-07-23
version: '8.x-1.12'
project: 'state_machine'
datestamp: 1721740594

View File

@@ -0,0 +1,46 @@
<?php
/**
* @file
* Test module for State Machine.
*/
use Drupal\views\EntityViewsData;
/**
* Implements hook_entity_type_build().
*/
function state_machine_test_entity_type_build(array &$entity_types) {
$entity_types['entity_test_with_bundle']->setHandlerClass('views_data', EntityViewsData::class);
}
/**
* Workflow callback for the "Second" bundle of entity_test_with_bundle.
*/
function state_machine_test_workflow_callback() {
return 'validation';
}
/**
* Implements hook_entity_type_alter().
*/
function state_machine_test_entity_type_alter(array &$entity_types) {
/** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
if (isset($entity_types['entity_test_with_bundle'])) {
$entity_types['entity_test_with_bundle']->setLinkTemplate('state-transition-form', '/entity_test_with_bundle/{entity_test_with_bundle}/{field_name}/{transition_id}');
}
}
/**
* Implements hook_module_implements_alter().
*/
function state_machine_test_module_implements_alter(&$implementations, $hook) {
// Ensure state_machine_entity_type_alter() runs after
// state_machine_test_entity_type_alter() which specifies the link template
// which is checked in state_machine_entity_type_alter().
if ($hook === 'entity_type_alter') {
$group = $implementations['state_machine'];
unset($implementations['state_machine']);
$implementations['state_machine'] = $group;
}
}

View File

@@ -0,0 +1,17 @@
services:
state_machine_test.fulfillment_guard:
class: Drupal\state_machine_test\Guard\FulfillmentGuard
tags:
- { name: state_machine.guard, group: entity_test_with_bundle }
state_machine_test.generic_guard:
class: Drupal\state_machine_test\Guard\GenericGuard
arguments: ['@current_user']
tags:
- { name: state_machine.guard }
state_machine_test.event_subscriber:
class: Drupal\state_machine_test\EventSubscriber\WorkflowTransitionEventSubscriber
arguments: ['@messenger']
tags:
- { name: event_subscriber }

View File

@@ -0,0 +1,3 @@
entity_test_with_bundle:
label: Test
entity_type: entity_test_with_bundle

View File

@@ -0,0 +1,101 @@
default:
id: default
label: Default
group: entity_test_with_bundle
states:
new:
label: New
fulfillment:
label: Fulfillment
completed:
label: Completed
canceled:
label: Canceled
transitions:
create:
label: Create
from: [new]
to: fulfillment
fulfill:
label: Fulfill
from: [fulfillment]
to: completed
cancel:
label: Cancel
from: [new, fulfillment]
to: canceled
validation:
id: validation
label: Validation
group: entity_test_with_bundle
states:
new:
label: New
validation:
label: Validation
fulfillment:
label: Fulfillment
completed:
label: Completed
canceled:
label: Canceled
transitions:
create:
label: Create
from: [new]
to: validation
validate:
label: Validate
from: [validation]
to: fulfillment
fulfill:
label: Fulfill
from: [fulfillment]
to: completed
cancel:
label: Cancel
from: [new, validation, fulfillment]
to: canceled
two_transitions:
id: two_transitions
label: Two transitions
group: entity_test_with_bundle
states:
new:
label: New
completed:
label: Completed
transitions:
complete1:
label: Complete1
from: [new]
to: completed
complete2:
label: Complete2
from: [new]
to: completed
same_transition:
id: same_transition
label: Contains transition to same state.
group: entity_test_with_bundle
states:
new:
label: New
completed:
label: Completed
transitions:
complete1:
label: Complete1
from: [new]
to: completed
complete2:
label: Complete2
from: [new]
to: completed
same:
label: Stay same
from: [new]
to: new

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' => [],
],
];
}
}