Update core 8.3.0

This commit is contained in:
Rob Davies 2017-04-13 15:53:35 +01:00
parent da7a7918f8
commit cd7a898e66
6144 changed files with 132297 additions and 87747 deletions

View file

@ -0,0 +1,2 @@
workflows.workflow.*.third_party.workflow_third_party_settings_test:
type: ignore

View file

@ -0,0 +1,8 @@
name: 'Workflow Third Party Settings Test'
type: module
description: 'Allows third party settings on workflows to be tested.'
package: Testing
version: VERSION
core: 8.x
dependencies:
- workflows

View file

@ -0,0 +1,42 @@
workflow.type_settings.workflow_type_test:
type: mapping
label: 'Workflow test type settings'
mapping:
states:
type: sequence
sequence:
type: ignore
workflow.type_settings.workflow_type_required_state_test:
type: mapping
label: 'Workflow test type settings'
mapping:
states:
type: sequence
sequence:
type: ignore
workflow.type_settings.workflow_type_complex_test:
type: mapping
label: 'Workflow complex test type settings'
mapping:
states:
type: sequence
label: 'Additional state configuration'
sequence:
type: mapping
label: 'States'
mapping:
extra:
type: string
label: 'Extra information'
transitions:
type: sequence
label: 'Additional transition configuration'
sequence:
type: mapping
label: 'Transitions'
mapping:
extra:
type: string
label: 'Extra information'

View file

@ -0,0 +1,90 @@
<?php
namespace Drupal\workflow_type_test;
use Drupal\workflows\StateInterface;
/**
* A value object representing a workflow state.
*/
class DecoratedState implements StateInterface {
/**
* The vanilla state object from the Workflow module.
*
* @var \Drupal\workflows\StateInterface
*/
protected $state;
/**
* Extra information added to state.
*
* @var string
*/
protected $extra;
/**
* DecoratedState constructor.
*
* @param \Drupal\workflows\StateInterface $state
* The vanilla state object from the Workflow module.
* @param string $extra
* (optional) Extra information stored on the state. Defaults to ''.
*/
public function __construct(StateInterface $state, $extra = '') {
$this->state = $state;
$this->extra = $extra;
}
/**
* Gets the extra information stored on the state.
*
* @return string
*/
public function getExtra() {
return $this->extra;
}
/**
* {@inheritdoc}
*/
public function id() {
return $this->state->id();
}
/**
* {@inheritdoc}
*/
public function label() {
return $this->state->label();
}
/**
* {@inheritdoc}
*/
public function weight() {
return $this->state->weight();
}
/**
* {@inheritdoc}
*/
public function canTransitionTo($to_state_id) {
return $this->state->canTransitionTo($to_state_id);
}
/**
* {@inheritdoc}
*/
public function getTransitionTo($to_state_id) {
return $this->state->getTransitionTo($to_state_id);
}
/**
* {@inheritdoc}
*/
public function getTransitions() {
return $this->state->getTransitions();
}
}

View file

@ -0,0 +1,83 @@
<?php
namespace Drupal\workflow_type_test;
use Drupal\workflows\TransitionInterface;
/**
* A value object representing a workflow transition.
*/
class DecoratedTransition implements TransitionInterface {
/**
* The vanilla transition object from the Workflow module.
*
* @var \Drupal\workflows\TransitionInterface
*/
protected $transition;
/**
* Extra information added to transition.
*
* @var string
*/
protected $extra;
/**
* DecoratedTransition constructor.
*
* @param \Drupal\workflows\TransitionInterface $transition
* The vanilla transition object from the Workflow module.
* @param string $extra
* (optional) Extra information stored on the transition. Defaults to ''.
*/
public function __construct(TransitionInterface $transition, $extra = '') {
$this->transition = $transition;
$this->extra = $extra;
}
/**
* Gets the extra information stored on the transition.
*
* @return string
*/
public function getExtra() {
return $this->extra;
}
/**
* {@inheritdoc}
*/
public function id() {
return $this->transition->id();
}
/**
* {@inheritdoc}
*/
public function label() {
return $this->transition->label();
}
/**
* {@inheritdoc}
*/
public function from() {
return $this->transition->from();
}
/**
* {@inheritdoc}
*/
public function to() {
return $this->transition->to();
}
/**
* {@inheritdoc}
*/
public function weight() {
return $this->transition->weight();
}
}

View file

@ -0,0 +1,91 @@
<?php
namespace Drupal\workflow_type_test\Plugin\WorkflowType;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\workflows\Plugin\WorkflowTypeBase;
use Drupal\workflows\StateInterface;
use Drupal\workflows\TransitionInterface;
use Drupal\workflows\WorkflowInterface;
use Drupal\workflow_type_test\DecoratedState;
use Drupal\workflow_type_test\DecoratedTransition;
/**
* Test workflow type.
*
* @WorkflowType(
* id = "workflow_type_complex_test",
* label = @Translation("Workflow Type Complex Test"),
* )
*/
class ComplexTestType extends WorkflowTypeBase {
use StringTranslationTrait;
/**
* {@inheritDoc}
*/
public function decorateState(StateInterface $state) {
if (isset($this->configuration['states'][$state->id()])) {
$state = new DecoratedState($state, $this->configuration['states'][$state->id()]['extra']);
}
else {
$state = new DecoratedState($state);
}
return $state;
}
/**
* {@inheritDoc}
*/
public function decorateTransition(TransitionInterface $transition) {
if (isset($this->configuration['transitions'][$transition->id()])) {
$transition = new DecoratedTransition($transition, $this->configuration['transitions'][$transition->id()]['extra']);
}
else {
$transition = new DecoratedTransition($transition);
}
return $transition;
}
/**
* {@inheritdoc}
*/
public function buildStateConfigurationForm(FormStateInterface $form_state, WorkflowInterface $workflow, StateInterface $state = NULL) {
/** @var \Drupal\workflow_type_test\DecoratedState $state */
$form = [];
$form['extra'] = [
'#type' => 'textfield',
'#title' => $this->t('Extra'),
'#description' => $this->t('Extra information added to state'),
'#default_value' => isset($state) ? $state->getExtra() : FALSE,
];
return $form;
}
/**
* {@inheritdoc}
*/
public function buildTransitionConfigurationForm(FormStateInterface $form_state, WorkflowInterface $workflow, TransitionInterface $transition = NULL) {
/** @var \Drupal\workflow_type_test\DecoratedTransition $transition */
$form = [];
$form['extra'] = [
'#type' => 'textfield',
'#title' => $this->t('Extra'),
'#description' => $this->t('Extra information added to transition'),
'#default_value' => isset($transition) ? $transition->getExtra() : FALSE,
];
return $form;
}
/**
* {@inheritdoc}
*/
public function onDependencyRemoval(array $dependencies) {
// Always return TRUE to allow the logic in
// \Drupal\workflows\Entity\Workflow::onDependencyRemoval() to be tested.
return TRUE;
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace Drupal\workflow_type_test\Plugin\WorkflowType;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\workflows\Plugin\WorkflowTypeBase;
use Drupal\workflows\WorkflowInterface;
/**
* Test workflow type.
*
* @WorkflowType(
* id = "workflow_type_required_state_test",
* label = @Translation("Required State Type Test"),
* required_states = {
* "fresh",
* "rotten",
* }
* )
*/
class RequiredStateTestType extends WorkflowTypeBase {
use StringTranslationTrait;
/**
* {@inheritdoc}
*/
public function initializeWorkflow(WorkflowInterface $workflow) {
$workflow
->addState('fresh', $this->t('Fresh'))
->setStateWeight('fresh', -5)
->addState('rotten', $this->t('Rotten'))
->addTransition('rot', $this->t('Rot'), ['fresh'], 'rotten');
return $workflow;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
// No configuration is stored for the test type.
return [];
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Drupal\workflow_type_test\Plugin\WorkflowType;
use Drupal\workflows\Plugin\WorkflowTypeBase;
/**
* Test workflow type.
*
* @WorkflowType(
* id = "workflow_type_test",
* label = @Translation("Workflow Type Test"),
* )
*/
class TestType extends WorkflowTypeBase {
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
// No configuration is stored for the test type.
return [];
}
/**
* {@inheritdoc}
*/
public function getRequiredStates() {
// Normally this is obtained from the annotation but we get from state to
// allow dynamic testing.
return \Drupal::state()->get('workflow_type_test.required_states', []);
}
}

View file

@ -0,0 +1,8 @@
name: 'Workflow Type Test'
type: module
description: 'Provides a workflow type plugin for testing.'
package: Testing
version: VERSION
core: 8.x
dependencies:
- workflows

View file

@ -0,0 +1,54 @@
<?php
namespace Drupal\Tests\workflows\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests workflow UI when there are no types.
*
* @group workflows
*/
class WorkflowUiNoTypeTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['workflows', 'block'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// We're testing local actions.
$this->drupalPlaceBlock('local_actions_block');
}
/**
* Tests the creation of a workflow through the UI.
*/
public function testWorkflowUiWithNoType() {
$this->drupalLogin($this->createUser(['access administration pages', 'administer workflows']));
$this->drupalGet('admin/config/workflow/workflows/add');
// There are no workflow types so this should be a 403.
$this->assertSession()->statusCodeEquals(403);
$this->drupalGet('admin/config/workflow/workflows');
$this->assertSession()->pageTextContains('There are no workflow types available. In order to create workflows you need to install a module that provides a workflow type. For example, the Content Moderation module provides a workflow type that enables workflows for content entities.');
$this->assertSession()->pageTextNotContains('Add workflow');
$this->container->get('module_installer')->install(['workflow_type_test']);
// The render cache needs to be cleared because although the cache tags are
// correctly set the render cache does not pick it up.
\Drupal::cache('render')->deleteAll();
$this->drupalGet('admin/config/workflow/workflows');
$this->assertSession()->pageTextNotContains('There are no workflow types available. In order to create workflows you need to install a module that provides a workflow type. For example, the Content Moderation module provides a workflow type that enables workflows for content entities.');
$this->assertSession()->linkExists('Add workflow');
$this->assertSession()->pageTextContains('There is no Workflow yet.');
}
}

View file

@ -0,0 +1,302 @@
<?php
namespace Drupal\Tests\workflows\Functional;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
use Drupal\workflows\Entity\Workflow;
/**
* Tests workflow creation UI.
*
* @group workflows
*/
class WorkflowUiTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['workflows', 'workflow_type_test', 'block'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// We're testing local actions.
$this->drupalPlaceBlock('local_actions_block');
}
/**
* Tests route access/permissions.
*/
public function testAccess() {
// Create a minimal workflow for testing.
$workflow = Workflow::create(['id' => 'test', 'type' => 'workflow_type_test']);
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
->save();
$paths = [
'admin/config/workflow/workflows',
'admin/config/workflow/workflows/add',
'admin/config/workflow/workflows/manage/test',
'admin/config/workflow/workflows/manage/test/delete',
'admin/config/workflow/workflows/manage/test/add_state',
'admin/config/workflow/workflows/manage/test/state/published',
'admin/config/workflow/workflows/manage/test/state/published/delete',
'admin/config/workflow/workflows/manage/test/add_transition',
'admin/config/workflow/workflows/manage/test/transition/publish',
'admin/config/workflow/workflows/manage/test/transition/publish/delete',
];
foreach ($paths as $path) {
$this->drupalGet($path);
// No access.
$this->assertSession()->statusCodeEquals(403);
}
$this->drupalLogin($this->createUser(['administer workflows']));
foreach ($paths as $path) {
$this->drupalGet($path);
// User has access.
$this->assertSession()->statusCodeEquals(200);
}
// Ensure that default states can not be deleted.
\Drupal::state()->set('workflow_type_test.required_states', ['published']);
$this->drupalGet('admin/config/workflow/workflows/manage/test/state/published/delete');
$this->assertSession()->statusCodeEquals(403);
\Drupal::state()->set('workflow_type_test.required_states', []);
// Delete one of the states and ensure the other test cannot be deleted.
$this->drupalGet('admin/config/workflow/workflows/manage/test/state/published/delete');
$this->submitForm([], 'Delete');
$this->drupalGet('admin/config/workflow/workflows/manage/test/state/draft/delete');
$this->assertSession()->statusCodeEquals(403);
}
/**
* Tests the creation of a workflow through the UI.
*/
public function testWorkflowCreation() {
$workflow_storage = $this->container->get('entity_type.manager')->getStorage('workflow');
/** @var \Drupal\workflows\WorkflowInterface $workflow */
$this->drupalLogin($this->createUser(['access administration pages', 'administer workflows']));
$this->drupalGet('admin/config/workflow');
$this->assertSession()->linkByHrefExists('admin/config/workflow/workflows');
$this->clickLink('Workflows');
$this->assertSession()->pageTextContains('Workflows');
$this->assertSession()->pageTextContains('There is no Workflow yet.');
$this->clickLink('Add workflow');
$this->submitForm(['label' => 'Test', 'id' => 'test', 'workflow_type' => 'workflow_type_test'], 'Save');
$this->assertSession()->pageTextContains('Created the Test Workflow.');
$this->assertSession()->addressEquals('admin/config/workflow/workflows/manage/test/add_state');
$this->drupalGet('/admin/config/workflow/workflows/manage/test');
$this->assertSession()->pageTextContains('This workflow has no states and will be disabled until there is at least one, add a new state.');
$this->assertSession()->pageTextContains('There are no states yet.');
$this->clickLink('Add a new state');
$this->submitForm(['label' => 'Published', 'id' => 'published'], 'Save');
$this->assertSession()->pageTextContains('Created Published state.');
$workflow = $workflow_storage->loadUnchanged('test');
$this->assertFalse($workflow->getState('published')->canTransitionTo('published'), 'No default transition from published to published exists.');
$this->clickLink('Add a new state');
// Don't create a draft to draft transition by default.
$this->submitForm(['label' => 'Draft', 'id' => 'draft'], 'Save');
$this->assertSession()->pageTextContains('Created Draft state.');
$workflow = $workflow_storage->loadUnchanged('test');
$this->assertFalse($workflow->getState('draft')->canTransitionTo('draft'), 'Can not transition from draft to draft');
$this->clickLink('Add a new transition');
$this->submitForm(['id' => 'publish', 'label' => 'Publish', 'from[draft]' => 'draft', 'to' => 'published'], 'Save');
$this->assertSession()->pageTextContains('Created Publish transition.');
$workflow = $workflow_storage->loadUnchanged('test');
$this->assertTrue($workflow->getState('draft')->canTransitionTo('published'), 'Can transition from draft to published');
$this->clickLink('Add a new transition');
$this->submitForm(['id' => 'create_new_draft', 'label' => 'Create new draft', 'from[draft]' => 'draft', 'to' => 'draft'], 'Save');
$this->assertSession()->pageTextContains('Created Create new draft transition.');
$workflow = $workflow_storage->loadUnchanged('test');
$this->assertTrue($workflow->getState('draft')->canTransitionTo('draft'), 'Can transition from draft to draft');
// The fist state to edit on the page should be published.
$this->clickLink('Edit');
$this->assertSession()->fieldValueEquals('label', 'Published');
// Change the label.
$this->submitForm(['label' => 'Live'], 'Save');
$this->assertSession()->pageTextContains('Saved Live state.');
// Allow published to draft.
$this->clickLink('Edit', 3);
$this->submitForm(['from[published]' => 'published'], 'Save');
$this->assertSession()->pageTextContains('Saved Create new draft transition.');
$workflow = $workflow_storage->loadUnchanged('test');
$this->assertTrue($workflow->getState('published')->canTransitionTo('draft'), 'Can transition from published to draft');
// Try creating a duplicate transition.
$this->clickLink('Add a new transition');
$this->submitForm(['id' => 'create_new_draft', 'label' => 'Create new draft', 'from[published]' => 'published', 'to' => 'draft'], 'Save');
$this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
// Try creating a transition which duplicates the states of another.
$this->submitForm(['id' => 'create_new_draft2', 'label' => 'Create new draft again', 'from[published]' => 'published', 'to' => 'draft'], 'Save');
$this->assertSession()->pageTextContains('The transition from Live to Draft already exists.');
// Create a new transition.
$this->submitForm(['id' => 'save_and_publish', 'label' => 'Save and publish', 'from[published]' => 'published', 'to' => 'published'], 'Save');
$this->assertSession()->pageTextContains('Created Save and publish transition.');
// Edit the new transition and try to add an existing transition.
$this->clickLink('Edit', 4);
$this->submitForm(['from[draft]' => 'draft'], 'Save');
$this->assertSession()->pageTextContains('The transition from Draft to Live already exists.');
// Delete the transition.
$workflow = $workflow_storage->loadUnchanged('test');
$this->assertTrue($workflow->hasTransitionFromStateToState('published', 'published'), 'Can transition from published to published');
$this->clickLink('Delete');
$this->assertSession()->pageTextContains('Are you sure you want to delete Save and publish from Test?');
$this->submitForm([], 'Delete');
$workflow = $workflow_storage->loadUnchanged('test');
$this->assertFalse($workflow->hasTransitionFromStateToState('published', 'published'), 'Cannot transition from published to published');
// Try creating a duplicate state.
$this->drupalGet('admin/config/workflow/workflows/manage/test');
$this->clickLink('Add a new state');
$this->submitForm(['label' => 'Draft', 'id' => 'draft'], 'Save');
$this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
// Ensure that weight changes the state ordering.
$workflow = $workflow_storage->loadUnchanged('test');
$this->assertEquals('published', $workflow->getInitialState()->id());
$this->drupalGet('admin/config/workflow/workflows/manage/test');
$this->submitForm(['states[draft][weight]' => '-1'], 'Save');
$workflow = $workflow_storage->loadUnchanged('test');
$this->assertEquals('draft', $workflow->getInitialState()->id());
// This will take us to the list of workflows, so we need to edit the
// workflow again.
$this->clickLink('Edit');
// Ensure that weight changes the transition ordering.
$this->assertEquals(['publish', 'create_new_draft'], array_keys($workflow->getTransitions()));
$this->drupalGet('admin/config/workflow/workflows/manage/test');
$this->submitForm(['transitions[create_new_draft][weight]' => '-1'], 'Save');
$workflow = $workflow_storage->loadUnchanged('test');
$this->assertEquals(['create_new_draft', 'publish'], array_keys($workflow->getTransitions()));
// This will take us to the list of workflows, so we need to edit the
// workflow again.
$this->clickLink('Edit');
// Ensure that a delete link for the published state exists before deleting
// the draft state.
$published_delete_link = Url::fromRoute('entity.workflow.delete_state_form', [
'workflow' => $workflow->id(),
'workflow_state' => 'published',
])->toString();
$draft_delete_link = Url::fromRoute('entity.workflow.delete_state_form', [
'workflow' => $workflow->id(),
'workflow_state' => 'draft',
])->toString();
$this->assertSession()->elementContains('css', 'tr[data-drupal-selector="edit-states-published"]', 'Delete');
$this->assertSession()->linkByHrefExists($published_delete_link);
$this->assertSession()->linkByHrefExists($draft_delete_link);
// Make the published state a default state and ensure it is no longer
// linked.
\Drupal::state()->set('workflow_type_test.required_states', ['published']);
$this->getSession()->reload();
$this->assertSession()->linkByHrefNotExists($published_delete_link);
$this->assertSession()->linkByHrefExists($draft_delete_link);
$this->assertSession()->elementNotContains('css', 'tr[data-drupal-selector="edit-states-published"]', 'Delete');
\Drupal::state()->set('workflow_type_test.required_states', []);
$this->getSession()->reload();
$this->assertSession()->elementContains('css', 'tr[data-drupal-selector="edit-states-published"]', 'Delete');
$this->assertSession()->linkByHrefExists($published_delete_link);
$this->assertSession()->linkByHrefExists($draft_delete_link);
// Delete the Draft state.
$this->clickLink('Delete');
$this->assertSession()->pageTextContains('Are you sure you want to delete Draft from Test?');
$this->submitForm([], 'Delete');
$this->assertSession()->pageTextContains('State Draft deleted.');
$workflow = $workflow_storage->loadUnchanged('test');
$this->assertFalse($workflow->hasState('draft'), 'Draft state deleted');
$this->assertTrue($workflow->hasState('published'), 'Workflow still has published state');
// The last state cannot be deleted so the only delete link on the page will
// be for the workflow.
$this->assertSession()->linkByHrefNotExists($published_delete_link);
$this->clickLink('Delete');
$this->assertSession()->pageTextContains('Are you sure you want to delete Test?');
$this->submitForm([], 'Delete');
$this->assertSession()->pageTextContains('Workflow Test deleted.');
$this->assertSession()->pageTextContains('There is no Workflow yet.');
$this->assertNull($workflow_storage->loadUnchanged('test'), 'The test workflow has been deleted');
// Ensure that workflow types that implement
// \Drupal\workflows\WorkflowTypeInterface::initializeWorkflow() are
// initialized correctly.
$this->drupalGet('admin/config/workflow/workflows');
$this->clickLink('Add workflow');
$this->submitForm(['label' => 'Test 2', 'id' => 'test2', 'workflow_type' => 'workflow_type_required_state_test'], 'Save');
$this->assertSession()->addressEquals('admin/config/workflow/workflows/manage/test2');
$workflow = $workflow_storage->loadUnchanged('test2');
$this->assertTrue($workflow->hasState('fresh'), 'The workflow has the "fresh" state');
$this->assertTrue($workflow->hasState('rotten'), 'The workflow has the "rotten" state');
$this->assertTrue($workflow->hasTransition('rot'), 'The workflow has the "rot" transition');
$this->assertSession()->pageTextContains('Fresh');
$this->assertSession()->pageTextContains('Rotten');
}
/**
* Tests that workflow types can add form fields to states and transitions.
*/
public function testWorkflowDecoration() {
// Create a minimal workflow for testing.
$workflow = Workflow::create(['id' => 'test', 'type' => 'workflow_type_complex_test']);
$workflow
->addState('published', 'Published')
->addTransition('publish', 'Publish', ['published'], 'published')
->save();
$this->assertEquals('', $workflow->getState('published')->getExtra());
$this->assertEquals('', $workflow->getTransition('publish')->getExtra());
$this->drupalLogin($this->createUser(['administer workflows']));
// Add additional state information when editing.
$this->drupalGet('admin/config/workflow/workflows/manage/test/state/published');
$this->assertSession()->pageTextContains('Extra information added to state');
$this->submitForm(['type_settings[workflow_type_complex_test][extra]' => 'Extra state information'], 'Save');
// Add additional transition information when editing.
$this->drupalGet('admin/config/workflow/workflows/manage/test/transition/publish');
$this->assertSession()->pageTextContains('Extra information added to transition');
$this->submitForm(['type_settings[workflow_type_complex_test][extra]' => 'Extra transition information'], 'Save');
$workflow_storage = $this->container->get('entity_type.manager')->getStorage('workflow');
/** @var \Drupal\workflows\WorkflowInterface $workflow */
$workflow = $workflow_storage->loadUnchanged('test');
$this->assertEquals('Extra state information', $workflow->getState('published')->getExtra());
$this->assertEquals('Extra transition information', $workflow->getTransition('publish')->getExtra());
// Add additional state information when adding.
$this->drupalGet('admin/config/workflow/workflows/manage/test/add_state');
$this->assertSession()->pageTextContains('Extra information added to state');
$this->submitForm(['label' => 'Draft', 'id' => 'draft', 'type_settings[workflow_type_complex_test][extra]' => 'Extra state information on add'], 'Save');
// Add additional transition information when adding.
$this->drupalGet('admin/config/workflow/workflows/manage/test/add_transition');
$this->assertSession()->pageTextContains('Extra information added to transition');
$this->submitForm(['id' => 'draft_published', 'label' => 'Publish', 'from[draft]' => 'draft', 'to' => 'published', 'type_settings[workflow_type_complex_test][extra]' => 'Extra transition information on add'], 'Save');
$workflow = $workflow_storage->loadUnchanged('test');
$this->assertEquals('Extra state information on add', $workflow->getState('draft')->getExtra());
$this->assertEquals('Extra transition information on add', $workflow->getTransition('draft_published')->getExtra());
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Drupal\Tests\workflows\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\workflows\Entity\Workflow;
use Drupal\workflow_type_test\DecoratedState;
use Drupal\workflow_type_test\DecoratedTransition;
/**
* Workflow entity tests that require modules or storage.
*
* @coversDefaultClass \Drupal\workflows\Entity\Workflow
*
* @group workflows
*/
class ComplexWorkflowTypeTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['workflows', 'workflow_type_test'];
/**
* Tests a workflow type that decorates transitions and states.
*
* @covers ::getState
* @covers ::getTransition
*/
public function testComplexType() {
$workflow = new Workflow(['id' => 'test', 'type' => 'workflow_type_complex_test'], 'workflow');
$workflow
->addState('draft', 'Draft')
->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft');
$this->assertInstanceOf(DecoratedState::class, $workflow->getState('draft'));
$this->assertInstanceOf(DecoratedTransition::class, $workflow->getTransition('create_new_draft'));
}
/**
* @covers ::loadMultipleByType
*/
public function testLoadMultipleByType() {
$workflow1 = new Workflow(['id' => 'test1', 'type' => 'workflow_type_complex_test'], 'workflow');
$workflow1->save();
$workflow2 = new Workflow(['id' => 'test2', 'type' => 'workflow_type_complex_test'], 'workflow');
$workflow2->save();
$workflow3 = new Workflow(['id' => 'test3', 'type' => 'workflow_type_test'], 'workflow');
$workflow3->save();
$this->assertEquals(['test1', 'test2'], array_keys(Workflow::loadMultipleByType('workflow_type_complex_test')));
$this->assertEquals(['test3'], array_keys(Workflow::loadMultipleByType('workflow_type_test')));
$this->assertEquals([], Workflow::loadMultipleByType('a_type_that_does_not_exist'));
}
}

View file

@ -0,0 +1,120 @@
<?php
namespace Drupal\Tests\workflows\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\workflows\Entity\Workflow;
use Drupal\workflows\Exception\RequiredStateMissingException;
/**
* Tests Workflow type's required states and configuration initialization.
*
* @coversDefaultClass \Drupal\workflows\Plugin\WorkflowTypeBase
*
* @group workflows
*/
class RequiredStatesTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['workflows', 'workflow_type_test'];
/**
* @covers ::getRequiredStates
* @covers ::initializeWorkflow
* @covers ::__construct
*/
public function testGetRequiredStates() {
$workflow = new Workflow([
'id' => 'test',
'type' => 'workflow_type_required_state_test',
], 'workflow');
$workflow = $workflow->getTypePlugin()->initializeWorkflow($workflow);
$workflow->save();
$this->assertEquals(['fresh', 'rotten'], $workflow->getTypePlugin()
->getRequiredStates());
// Ensure that the workflow has the default configuration.
$this->assertTrue($workflow->hasState('rotten'));
$this->assertTrue($workflow->hasState('fresh'));
$this->assertTrue($workflow->hasTransitionFromStateToState('fresh', 'rotten'));
}
/**
* @covers \Drupal\workflows\Entity\Workflow::preSave
*/
public function testDeleteRequiredStateAPI() {
$workflow = new Workflow([
'id' => 'test',
'type' => 'workflow_type_required_state_test',
], 'workflow');
$workflow = $workflow->getTypePlugin()->initializeWorkflow($workflow);
$workflow->save();
// Ensure that required states can't be deleted.
$this->setExpectedException(RequiredStateMissingException::class, "Required State Type Test' requires states with the ID 'fresh' in workflow 'test'");
$workflow->deleteState('fresh')->save();
}
/**
* @covers \Drupal\workflows\Entity\Workflow::preSave
*/
public function testNoStatesRequiredStateAPI() {
$workflow = new Workflow([
'id' => 'test',
'type' => 'workflow_type_required_state_test',
], 'workflow');
$this->setExpectedException(RequiredStateMissingException::class, "Required State Type Test' requires states with the ID 'fresh', 'rotten' in workflow 'test'");
$workflow->save();
}
/**
* Ensures that initialized configuration can be changed.
*/
public function testChangeRequiredStateAPI() {
$workflow = new Workflow([
'id' => 'test',
'type' => 'workflow_type_required_state_test',
], 'workflow');
$workflow = $workflow->getTypePlugin()->initializeWorkflow($workflow);
$workflow->save();
// Ensure states added by default configuration can be changed.
$this->assertEquals('Fresh', $workflow->getState('fresh')->label());
$workflow
->setStateLabel('fresh', 'Fresher')
->save();
$this->assertEquals('Fresher', $workflow->getState('fresh')->label());
// Ensure transitions can be altered.
$workflow
->addState('cooked', 'Cooked')
->setTransitionFromStates('rot', ['fresh', 'cooked'])
->save();
$this->assertTrue($workflow->hasTransitionFromStateToState('fresh', 'rotten'));
$this->assertTrue($workflow->hasTransitionFromStateToState('cooked', 'rotten'));
$workflow
->setTransitionFromStates('rot', ['cooked'])
->save();
$this->assertFalse($workflow->hasTransitionFromStateToState('fresh', 'rotten'));
$this->assertTrue($workflow->hasTransitionFromStateToState('cooked', 'rotten'));
// Ensure the default configuration does not cause ordering issues.
$workflow->addTransition('cook', 'Cook', ['fresh'], 'cooked')->save();
$this->assertSame([
'cooked',
'fresh',
'rotten',
], array_keys($workflow->get('states')));
$this->assertSame([
'cook',
'rot',
], array_keys($workflow->get('transitions')));
// Ensure that transitions can be deleted.
$workflow->deleteTransition('rot')->save();
$this->assertFalse($workflow->hasTransition('rot'));
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Drupal\Tests\workflows\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\workflows\Entity\Workflow;
/**
* Tests configuration dependencies in workflows.
*
* @coversDefaultClass \Drupal\workflows\Entity\Workflow
*
* @group workflows
*/
class WorkflowDependenciesTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['system', 'workflows', 'workflow_type_test', 'workflow_third_party_settings_test'];
/**
* Tests \Drupal\workflows\Entity\Workflow::onDependencyRemoval().
*/
public function testOnDependencyRemoval() {
// Create a workflow that has a dependency on a third party setting.
$workflow = Workflow::create(['id' => 'test3', 'type' => 'workflow_type_complex_test']);
$workflow->setThirdPartySetting('workflow_third_party_settings_test', 'key', 'value');
$workflow->save();
$this->assertSame(['workflow_third_party_settings_test', 'workflow_type_test'], $workflow->getDependencies()['module']);
// Uninstall workflow_third_party_settings_test to ensure
// \Drupal\workflows\Entity\Workflow::onDependencyRemoval() works as
// expected.
\Drupal::service('module_installer')->uninstall(['node', 'workflow_third_party_settings_test']);
$workflow = \Drupal::entityTypeManager()->getStorage('workflow')->loadUnchanged($workflow->id());
$this->assertSame(['workflow_type_test'], $workflow->getDependencies()['module']);
}
}

View file

@ -0,0 +1,131 @@
<?php
namespace Drupal\Tests\workflows\Unit;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Tests\UnitTestCase;
use Drupal\workflows\Entity\Workflow;
use Drupal\workflows\State;
use Drupal\workflows\WorkflowInterface;
use Drupal\workflows\WorkflowTypeInterface;
use Drupal\workflows\WorkflowTypeManager;
use Prophecy\Argument;
/**
* @coversDefaultClass \Drupal\workflows\State
*
* @group workflows
*/
class StateTest extends UnitTestCase {
/**
* Sets up the Workflow Type manager so that workflow entities can be used.
*/
protected function setUp() {
parent::setUp();
// Create a container so that the plugin manager and workflow type can be
// mocked.
$container = new ContainerBuilder();
$workflow_type = $this->prophesize(WorkflowTypeInterface::class);
$workflow_type->decorateState(Argument::any())->willReturnArgument(0);
$workflow_type->decorateTransition(Argument::any())->willReturnArgument(0);
$workflow_type->deleteState(Argument::any())->willReturn(NULL);
$workflow_type->deleteTransition(Argument::any())->willReturn(NULL);
$workflow_manager = $this->prophesize(WorkflowTypeManager::class);
$workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal());
$container->set('plugin.manager.workflows.type', $workflow_manager->reveal());
\Drupal::setContainer($container);
}
/**
* @covers ::__construct
* @covers ::id
* @covers ::label
* @covers ::weight
*/
public function testGetters() {
$state = new State(
$this->prophesize(WorkflowInterface::class)->reveal(),
'draft',
'Draft',
3
);
$this->assertEquals('draft', $state->id());
$this->assertEquals('Draft', $state->label());
$this->assertEquals(3, $state->weight());
}
/**
* @covers ::canTransitionTo
*/
public function testCanTransitionTo() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addTransition('publish', 'Publish', ['draft'], 'published');
$state = $workflow->getState('draft');
$this->assertTrue($state->canTransitionTo('published'));
$this->assertFalse($state->canTransitionTo('some_other_state'));
$workflow->deleteTransition('publish');
$this->assertFalse($state->canTransitionTo('published'));
}
/**
* @covers ::getTransitionTo
*/
public function testGetTransitionTo() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addTransition('publish', 'Publish', ['draft'], 'published');
$state = $workflow->getState('draft');
$transition = $state->getTransitionTo('published');
$this->assertEquals('Publish', $transition->label());
}
/**
* @covers ::getTransitionTo
*/
public function testGetTransitionToException() {
$this->setExpectedException(\InvalidArgumentException::class, "Can not transition to 'published' state");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->addState('draft', 'Draft');
$state = $workflow->getState('draft');
$state->getTransitionTo('published');
}
/**
* @covers ::getTransitions
*/
public function testGetTransitions() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addState('archived', 'Archived')
->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
->addTransition('publish', 'Publish', ['draft'], 'published')
->addTransition('archive', 'Archive', ['published'], 'archived');
$state = $workflow->getState('draft');
$transitions = $state->getTransitions();
$this->assertCount(2, $transitions);
$this->assertEquals('Create new draft', $transitions['create_new_draft']->label());
$this->assertEquals('Publish', $transitions['publish']->label());
}
/**
* @covers ::labelCallback
*/
public function testLabelCallback() {
$workflow = $this->prophesize(WorkflowInterface::class)->reveal();
$states = [
new State($workflow, 'draft', 'Draft'),
new State($workflow, 'published', 'Published'),
];
$this->assertEquals(['Draft', 'Published'], array_map([State::class, 'labelCallback'], $states));
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace Drupal\Tests\workflows\Unit;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Tests\UnitTestCase;
use Drupal\workflows\Entity\Workflow;
use Drupal\workflows\Transition;
use Drupal\workflows\WorkflowInterface;
use Drupal\workflows\WorkflowTypeInterface;
use Drupal\workflows\WorkflowTypeManager;
use Prophecy\Argument;
/**
* @coversDefaultClass \Drupal\workflows\Transition
*
* @group workflows
*/
class TransitionTest extends UnitTestCase {
/**
* Sets up the Workflow Type manager so that workflow entities can be used.
*/
protected function setUp() {
parent::setUp();
// Create a container so that the plugin manager and workflow type can be
// mocked.
$container = new ContainerBuilder();
$workflow_type = $this->prophesize(WorkflowTypeInterface::class);
$workflow_type->decorateState(Argument::any())->willReturnArgument(0);
$workflow_type->decorateTransition(Argument::any())->willReturnArgument(0);
$workflow_manager = $this->prophesize(WorkflowTypeManager::class);
$workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal());
$container->set('plugin.manager.workflows.type', $workflow_manager->reveal());
\Drupal::setContainer($container);
}
/**
* @covers ::__construct
* @covers ::id
* @covers ::label
*/
public function testGetters() {
$state = new Transition(
$this->prophesize(WorkflowInterface::class)->reveal(),
'draft_published',
'Publish',
['draft'],
'published'
);
$this->assertEquals('draft_published', $state->id());
$this->assertEquals('Publish', $state->label());
}
/**
* @covers ::from
* @covers ::to
*/
public function testFromAndTo() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addTransition('publish', 'Publish', ['draft'], 'published');
$state = $workflow->getState('draft');
$transition = $state->getTransitionTo('published');
$this->assertEquals($state, $transition->from()['draft']);
$this->assertEquals($workflow->getState('published'), $transition->to());
}
}

View file

@ -0,0 +1,679 @@
<?php
namespace Drupal\Tests\workflows\Unit;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Tests\UnitTestCase;
use Drupal\workflows\Entity\Workflow;
use Drupal\workflows\State;
use Drupal\workflows\Transition;
use Drupal\workflows\WorkflowTypeInterface;
use Drupal\workflows\WorkflowTypeManager;
use Prophecy\Argument;
/**
* @coversDefaultClass \Drupal\workflows\Entity\Workflow
*
* @group workflows
*/
class WorkflowTest extends UnitTestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a container so that the plugin manager and workflow type can be
// mocked.
$container = new ContainerBuilder();
$workflow_type = $this->prophesize(WorkflowTypeInterface::class);
$workflow_type->decorateState(Argument::any())->willReturnArgument(0);
$workflow_type->decorateTransition(Argument::any())->willReturnArgument(0);
$workflow_manager = $this->prophesize(WorkflowTypeManager::class);
$workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal());
$container->set('plugin.manager.workflows.type', $workflow_manager->reveal());
\Drupal::setContainer($container);
}
/**
* @covers ::addState
* @covers ::hasState
*/
public function testAddAndHasState() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$this->assertFalse($workflow->hasState('draft'));
// By default states are ordered in the order added.
$workflow->addState('draft', 'Draft');
$this->assertTrue($workflow->hasState('draft'));
$this->assertFalse($workflow->hasState('published'));
$this->assertEquals(0, $workflow->getState('draft')->weight());
// Adding a state does not set up a transition to itself.
$this->assertFalse($workflow->hasTransitionFromStateToState('draft', 'draft'));
// New states are added with a new weight 1 more than the current highest
// weight.
$workflow->addState('published', 'Published');
$this->assertEquals(1, $workflow->getState('published')->weight());
}
/**
* @covers ::addState
*/
public function testAddStateException() {
$this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' already exists in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->addState('draft', 'Draft');
$workflow->addState('draft', 'Draft');
}
/**
* @covers ::addState
*/
public function testAddStateInvalidIdException() {
$this->setExpectedException(\InvalidArgumentException::class, "The state ID 'draft-draft' must contain only lowercase letters, numbers, and underscores");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->addState('draft-draft', 'Draft');
}
/**
* @covers ::getStates
*/
public function testGetStates() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
// Getting states works when there are none.
$this->assertArrayEquals([], array_keys($workflow->getStates()));
$this->assertArrayEquals([], array_keys($workflow->getStates([])));
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addState('archived', 'Archived');
// States are stored in alphabetical key order.
$this->assertArrayEquals([
'archived',
'draft',
'published',
], array_keys($workflow->get('states')));
// Ensure we're returning state objects.
$this->assertInstanceOf(State::class, $workflow->getStates()['draft']);
// Passing in no IDs returns all states.
$this->assertArrayEquals(['draft', 'published', 'archived'], array_keys($workflow->getStates()));
// The order of states is by weight.
$workflow->setStateWeight('published', -1);
$this->assertArrayEquals(['published', 'draft', 'archived'], array_keys($workflow->getStates()));
// The label is also used for sorting if weights are equal.
$workflow->setStateWeight('archived', 0);
$this->assertArrayEquals(['published', 'archived', 'draft'], array_keys($workflow->getStates()));
// You can limit the states returned by passing in states IDs.
$this->assertArrayEquals(['archived', 'draft'], array_keys($workflow->getStates(['draft', 'archived'])));
// An empty array does not load all states.
$this->assertArrayEquals([], array_keys($workflow->getStates([])));
}
/**
* @covers ::getStates
*/
public function testGetStatesException() {
$this->setExpectedException(\InvalidArgumentException::class, "The state 'state_that_does_not_exist' does not exist in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->getStates(['state_that_does_not_exist']);
}
/**
* @covers ::getState
*/
public function testGetState() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
// By default states are ordered in the order added.
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addState('archived', 'Archived')
->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
->addTransition('publish', 'Publish', ['draft'], 'published');
// Ensure we're returning state objects and they are set up correctly
$this->assertInstanceOf(State::class, $workflow->getState('draft'));
$this->assertEquals('archived', $workflow->getState('archived')->id());
$this->assertEquals('Archived', $workflow->getState('archived')->label());
$draft = $workflow->getState('draft');
$this->assertTrue($draft->canTransitionTo('draft'));
$this->assertTrue($draft->canTransitionTo('published'));
$this->assertFalse($draft->canTransitionTo('archived'));
$this->assertEquals('Publish', $draft->getTransitionTo('published')->label());
$this->assertEquals(0, $draft->weight());
$this->assertEquals(1, $workflow->getState('published')->weight());
$this->assertEquals(2, $workflow->getState('archived')->weight());
}
/**
* @covers ::getState
*/
public function testGetStateException() {
$this->setExpectedException(\InvalidArgumentException::class, "The state 'state_that_does_not_exist' does not exist in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->getState('state_that_does_not_exist');
}
/**
* @covers ::setStateLabel
*/
public function testSetStateLabel() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->addState('draft', 'Draft');
$this->assertEquals('Draft', $workflow->getState('draft')->label());
$workflow->setStateLabel('draft', 'Unpublished');
$this->assertEquals('Unpublished', $workflow->getState('draft')->label());
}
/**
* @covers ::setStateLabel
*/
public function testSetStateLabelException() {
$this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->setStateLabel('draft', 'Draft');
}
/**
* @covers ::setStateWeight
*/
public function testSetStateWeight() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->addState('draft', 'Draft');
$this->assertEquals(0, $workflow->getState('draft')->weight());
$workflow->setStateWeight('draft', -10);
$this->assertEquals(-10, $workflow->getState('draft')->weight());
}
/**
* @covers ::setStateWeight
*/
public function testSetStateWeightException() {
$this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->setStateWeight('draft', 10);
}
/**
* @covers ::deleteState
*/
public function testDeleteState() {
// Create a container so that the plugin manager and workflow type can be
// mocked and test that
// \Drupal\workflows\WorkflowTypeInterface::deleteState() is called
// correctly.
$container = new ContainerBuilder();
$workflow_type = $this->prophesize(WorkflowTypeInterface::class);
$workflow_type->decorateState(Argument::any())->willReturnArgument(0);
$workflow_type->decorateTransition(Argument::any())->willReturnArgument(0);
$workflow_type->deleteState('draft')->shouldBeCalled();
$workflow_type->deleteTransition('create_new_draft')->shouldBeCalled();
$workflow_manager = $this->prophesize(WorkflowTypeManager::class);
$workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal());
$container->set('plugin.manager.workflows.type', $workflow_manager->reveal());
\Drupal::setContainer($container);
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
->addTransition('create_new_draft', 'Create new draft', ['draft', 'published'], 'draft');
$this->assertCount(2, $workflow->getStates());
$this->assertCount(2, $workflow->getState('published')->getTransitions());
$workflow->deleteState('draft');
$this->assertFalse($workflow->hasState('draft'));
$this->assertCount(1, $workflow->getStates());
$this->assertCount(1, $workflow->getState('published')->getTransitions());
}
/**
* @covers ::deleteState
*/
public function testDeleteStateException() {
$this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->deleteState('draft');
}
/**
* @covers ::deleteState
*/
public function testDeleteOnlyStateException() {
$this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' can not be deleted from workflow 'test' as it is the only state");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->addState('draft', 'Draft');
$workflow->deleteState('draft');
}
/**
* @covers ::getInitialState
*/
public function testGetInitialState() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
// By default states are ordered in the order added.
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addState('archived', 'Archived');
$this->assertEquals('draft', $workflow->getInitialState()->id());
// Make published the first state.
$workflow->setStateWeight('published', -1);
$this->assertEquals('published', $workflow->getInitialState()->id());
}
/**
* @covers ::addTransition
* @covers ::hasTransition
*/
public function testAddTransition() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
// By default states are ordered in the order added.
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published');
$this->assertFalse($workflow->getState('draft')->canTransitionTo('published'));
$workflow->addTransition('publish', 'Publish', ['draft'], 'published');
$this->assertTrue($workflow->getState('draft')->canTransitionTo('published'));
$this->assertEquals(0, $workflow->getTransition('publish')->weight());
$this->assertTrue($workflow->hasTransition('publish'));
$this->assertFalse($workflow->hasTransition('draft'));
$workflow->addTransition('save_publish', 'Save', ['published'], 'published');
$this->assertEquals(1, $workflow->getTransition('save_publish')->weight());
}
/**
* @covers ::addTransition
*/
public function testAddTransitionDuplicateException() {
$this->setExpectedException(\InvalidArgumentException::class, "The transition 'publish' already exists in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->addState('published', 'Published');
$workflow->addTransition('publish', 'Publish', ['published'], 'published');
$workflow->addTransition('publish', 'Publish', ['published'], 'published');
}
/**
* @covers ::addTransition
*/
public function testAddTransitionInvalidIdException() {
$this->setExpectedException(\InvalidArgumentException::class, "The transition ID 'publish-publish' must contain only lowercase letters, numbers, and underscores");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->addState('published', 'Published');
$workflow->addTransition('publish-publish', 'Publish', ['published'], 'published');
}
/**
* @covers ::addTransition
*/
public function testAddTransitionMissingFromException() {
$this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->addState('published', 'Published');
$workflow->addTransition('publish', 'Publish', ['draft'], 'published');
}
/**
* @covers ::addTransition
*/
public function testAddTransitionDuplicateTransitionStatesException() {
$this->setExpectedException(\InvalidArgumentException::class, "The 'publish' transition already allows 'draft' to 'published' transitions in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published');
$workflow->addTransition('publish', 'Publish', ['draft', 'published'], 'published');
$workflow->addTransition('draft_to_published', 'Publish a draft', ['draft'], 'published');
}
/**
* @covers ::addTransition
*/
public function testAddTransitionConsistentAfterFromCatch() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->addState('published', 'Published');
try {
$workflow->addTransition('publish', 'Publish', ['draft'], 'published');
}
catch (\InvalidArgumentException $e) {
}
// Ensure that the workflow is not left in an inconsistent state after an
// exception is thrown from Workflow::setTransitionFromStates() whilst
// calling Workflow::addTransition().
$this->assertFalse($workflow->hasTransition('publish'));
}
/**
* @covers ::addTransition
*/
public function testAddTransitionMissingToException() {
$this->setExpectedException(\InvalidArgumentException::class, "The state 'published' does not exist in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->addState('draft', 'Draft');
$workflow->addTransition('publish', 'Publish', ['draft'], 'published');
}
/**
* @covers ::getTransitions
* @covers ::setTransitionWeight
*/
public function testGetTransitions() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
// Getting transitions works when there are none.
$this->assertArrayEquals([], array_keys($workflow->getTransitions()));
$this->assertArrayEquals([], array_keys($workflow->getTransitions([])));
// By default states are ordered in the order added.
$workflow
->addState('a', 'A')
->addState('b', 'B')
->addTransition('a_b', 'A to B', ['a'], 'b')
->addTransition('a_a', 'A to A', ['a'], 'a');
// Transitions are stored in alphabetical key order in configuration.
$this->assertArrayEquals(['a_a', 'a_b'], array_keys($workflow->get('transitions')));
// Ensure we're returning transition objects.
$this->assertInstanceOf(Transition::class, $workflow->getTransitions()['a_a']);
// Passing in no IDs returns all transitions.
$this->assertArrayEquals(['a_b', 'a_a'], array_keys($workflow->getTransitions()));
// The order of states is by weight.
$workflow->setTransitionWeight('a_a', -1);
$this->assertArrayEquals(['a_a', 'a_b'], array_keys($workflow->getTransitions()));
// If all weights are equal it will fallback to labels.
$workflow->setTransitionWeight('a_a', 0);
$this->assertArrayEquals(['a_a', 'a_b'], array_keys($workflow->getTransitions()));
$workflow->setTransitionLabel('a_b', 'A B');
$this->assertArrayEquals(['a_b', 'a_a'], array_keys($workflow->getTransitions()));
// You can limit the states returned by passing in states IDs.
$this->assertArrayEquals(['a_a'], array_keys($workflow->getTransitions(['a_a'])));
// An empty array does not load all states.
$this->assertArrayEquals([], array_keys($workflow->getTransitions([])));
}
/**
* @covers ::getTransition
*/
public function testGetTransition() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
// By default states are ordered in the order added.
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addState('archived', 'Archived')
->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
->addTransition('publish', 'Publish', ['draft'], 'published');
// Ensure we're returning state objects and they are set up correctly
$this->assertInstanceOf(Transition::class, $workflow->getTransition('create_new_draft'));
$this->assertEquals('publish', $workflow->getTransition('publish')->id());
$this->assertEquals('Publish', $workflow->getTransition('publish')->label());
$transition = $workflow->getTransition('publish');
$this->assertEquals($workflow->getState('draft'), $transition->from()['draft']);
$this->assertEquals($workflow->getState('published'), $transition->to());
}
/**
* @covers ::getTransition
*/
public function testGetTransitionException() {
$this->setExpectedException(\InvalidArgumentException::class, "The transition 'transition_that_does_not_exist' does not exist in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->getTransition('transition_that_does_not_exist');
}
/**
* @covers ::getTransitionsForState
*/
public function testGetTransitionsForState() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
// By default states are ordered in the order added.
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addState('archived', 'Archived')
->addTransition('create_new_draft', 'Create new draft', ['archived', 'draft'], 'draft')
->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
->addTransition('archive', 'Archive', ['published'], 'archived');
$this->assertEquals(['create_new_draft', 'publish'], array_keys($workflow->getTransitionsForState('draft')));
$this->assertEquals(['create_new_draft'], array_keys($workflow->getTransitionsForState('draft', 'to')));
$this->assertEquals(['publish', 'archive'], array_keys($workflow->getTransitionsForState('published')));
$this->assertEquals(['publish'], array_keys($workflow->getTransitionsForState('published', 'to')));
$this->assertEquals(['create_new_draft'], array_keys($workflow->getTransitionsForState('archived', 'from')));
$this->assertEquals(['archive'], array_keys($workflow->getTransitionsForState('archived', 'to')));
}
/**
* @covers ::getTransitionFromStateToState
* @covers ::hasTransitionFromStateToState
*/
public function testGetTransitionFromStateToState() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
// By default states are ordered in the order added.
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addState('archived', 'Archived')
->addTransition('create_new_draft', 'Create new draft', ['archived', 'draft'], 'draft')
->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
->addTransition('archive', 'Archive', ['published'], 'archived');
$this->assertTrue($workflow->hasTransitionFromStateToState('draft', 'published'));
$this->assertFalse($workflow->hasTransitionFromStateToState('archived', 'archived'));
$transition = $workflow->getTransitionFromStateToState('published', 'archived');
$this->assertEquals('Archive', $transition->label());
}
/**
* @covers ::getTransitionFromStateToState
*/
public function testGetTransitionFromStateToStateException() {
$this->setExpectedException(\InvalidArgumentException::class, "The transition from 'archived' to 'archived' does not exist in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
// By default states are ordered in the order added.
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addState('archived', 'Archived')
->addTransition('create_new_draft', 'Create new draft', ['archived', 'draft'], 'draft')
->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
->addTransition('archive', 'Archive', ['published'], 'archived');
$workflow->getTransitionFromStateToState('archived', 'archived');
}
/**
* @covers ::setTransitionLabel
*/
public function testSetTransitionLabel() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addTransition('publish', 'Publish', ['draft'], 'published');
$this->assertEquals('Publish', $workflow->getTransition('publish')->label());
$workflow->setTransitionLabel('publish', 'Publish!');
$this->assertEquals('Publish!', $workflow->getTransition('publish')->label());
}
/**
* @covers ::setTransitionLabel
*/
public function testSetTransitionLabelException() {
$this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->addState('published', 'Published');
$workflow->setTransitionLabel('draft-published', 'Publish');
}
/**
* @covers ::setTransitionWeight
*/
public function testSetTransitionWeight() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addTransition('publish', 'Publish', ['draft'], 'published');
$this->assertEquals(0, $workflow->getTransition('publish')->weight());
$workflow->setTransitionWeight('publish', 10);
$this->assertEquals(10, $workflow->getTransition('publish')->weight());
}
/**
* @covers ::setTransitionWeight
*/
public function testSetTransitionWeightException() {
$this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->addState('published', 'Published');
$workflow->setTransitionWeight('draft-published', 10);
}
/**
* @covers ::setTransitionFromStates
*/
public function testSetTransitionFromStates() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addState('archived', 'Archived')
->addTransition('test', 'Test', ['draft'], 'draft');
$this->assertTrue($workflow->hasTransitionFromStateToState('draft', 'draft'));
$this->assertFalse($workflow->hasTransitionFromStateToState('published', 'draft'));
$this->assertFalse($workflow->hasTransitionFromStateToState('archived', 'draft'));
$workflow->setTransitionFromStates('test', ['draft', 'published', 'archived']);
$this->assertTrue($workflow->hasTransitionFromStateToState('draft', 'draft'));
$this->assertTrue($workflow->hasTransitionFromStateToState('published', 'draft'));
$this->assertTrue($workflow->hasTransitionFromStateToState('archived', 'draft'));
$workflow->setTransitionFromStates('test', ['published', 'archived']);
$this->assertFalse($workflow->hasTransitionFromStateToState('draft', 'draft'));
$this->assertTrue($workflow->hasTransitionFromStateToState('published', 'draft'));
$this->assertTrue($workflow->hasTransitionFromStateToState('archived', 'draft'));
}
/**
* @covers ::setTransitionFromStates
*/
public function testSetTransitionFromStatesMissingTransition() {
$this->setExpectedException(\InvalidArgumentException::class, "The transition 'test' does not exist in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addState('archived', 'Archived')
->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft');
$workflow->setTransitionFromStates('test', ['draft', 'published', 'archived']);
}
/**
* @covers ::setTransitionFromStates
*/
public function testSetTransitionFromStatesMissingState() {
$this->setExpectedException(\InvalidArgumentException::class, "The state 'published' does not exist in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow
->addState('draft', 'Draft')
->addState('archived', 'Archived')
->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft');
$workflow->setTransitionFromStates('create_new_draft', ['draft', 'published', 'archived']);
}
/**
* @covers ::setTransitionFromStates
*/
public function testSetTransitionFromStatesAlreadyExists() {
$this->setExpectedException(\InvalidArgumentException::class, "The 'create_new_draft' transition already allows 'draft' to 'draft' transitions in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow
->addState('draft', 'Draft')
->addState('archived', 'Archived')
->addState('needs_review', 'Needs Review')
->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
->addTransition('needs_review', 'Needs review', ['needs_review'], 'draft');
$workflow->setTransitionFromStates('needs_review', ['draft']);
}
/**
* @covers ::deleteTransition
*/
public function testDeleteTransition() {
// Create a container so that the plugin manager and workflow type can be
// mocked and test that
// \Drupal\workflows\WorkflowTypeInterface::deleteState() is called
// correctly.
$container = new ContainerBuilder();
$workflow_type = $this->prophesize(WorkflowTypeInterface::class);
$workflow_type->decorateState(Argument::any())->willReturnArgument(0);
$workflow_type->decorateTransition(Argument::any())->willReturnArgument(0);
$workflow_type->deleteTransition('publish')->shouldBeCalled();
$workflow_manager = $this->prophesize(WorkflowTypeManager::class);
$workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal());
$container->set('plugin.manager.workflows.type', $workflow_manager->reveal());
\Drupal::setContainer($container);
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow
->addState('draft', 'Draft')
->addState('published', 'Published')
->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
->addTransition('publish', 'Publish', ['draft'], 'published');
$this->assertTrue($workflow->getState('draft')->canTransitionTo('published'));
$workflow->deleteTransition('publish');
$this->assertFalse($workflow->getState('draft')->canTransitionTo('published'));
$this->assertTrue($workflow->getState('draft')->canTransitionTo('draft'));
}
/**
* @covers ::deleteTransition
*/
public function testDeleteTransitionException() {
$this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow 'test'");
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$workflow->addState('published', 'Published');
$workflow->deleteTransition('draft-published');
}
/**
* @covers ::status
*/
public function testStatus() {
$workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
$this->assertFalse($workflow->status());
$workflow->addState('published', 'Published');
$this->assertTrue($workflow->status());
}
}