Version 1
[yaffs-website] / web / core / modules / workflows / tests / src / Unit / WorkflowTest.php
1 <?php
2
3 namespace Drupal\Tests\workflows\Unit;
4
5 use Drupal\Core\DependencyInjection\ContainerBuilder;
6 use Drupal\Tests\UnitTestCase;
7 use Drupal\workflows\Entity\Workflow;
8 use Drupal\workflows\State;
9 use Drupal\workflows\Transition;
10 use Drupal\workflows\WorkflowTypeInterface;
11 use Drupal\workflows\WorkflowTypeManager;
12 use Prophecy\Argument;
13
14 /**
15  * @coversDefaultClass \Drupal\workflows\Entity\Workflow
16  *
17  * @group workflows
18  */
19 class WorkflowTest extends UnitTestCase {
20
21   /**
22    * {@inheritdoc}
23    */
24   protected function setUp() {
25     parent::setUp();
26     // Create a container so that the plugin manager and workflow type can be
27     // mocked.
28     $container = new ContainerBuilder();
29     $workflow_type = $this->prophesize(WorkflowTypeInterface::class);
30     $workflow_type->decorateState(Argument::any())->willReturnArgument(0);
31     $workflow_type->decorateTransition(Argument::any())->willReturnArgument(0);
32     $workflow_manager = $this->prophesize(WorkflowTypeManager::class);
33     $workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal());
34     $container->set('plugin.manager.workflows.type', $workflow_manager->reveal());
35     \Drupal::setContainer($container);
36   }
37
38   /**
39    * @covers ::addState
40    * @covers ::hasState
41    */
42   public function testAddAndHasState() {
43     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
44     $this->assertFalse($workflow->hasState('draft'));
45
46     // By default states are ordered in the order added.
47     $workflow->addState('draft', 'Draft');
48     $this->assertTrue($workflow->hasState('draft'));
49     $this->assertFalse($workflow->hasState('published'));
50     $this->assertEquals(0, $workflow->getState('draft')->weight());
51     // Adding a state does not set up a transition to itself.
52     $this->assertFalse($workflow->hasTransitionFromStateToState('draft', 'draft'));
53
54     // New states are added with a new weight 1 more than the current highest
55     // weight.
56     $workflow->addState('published', 'Published');
57     $this->assertEquals(1, $workflow->getState('published')->weight());
58   }
59
60   /**
61    * @covers ::addState
62    */
63   public function testAddStateException() {
64     $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' already exists in workflow 'test'");
65     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
66     $workflow->addState('draft', 'Draft');
67     $workflow->addState('draft', 'Draft');
68   }
69
70   /**
71    * @covers ::addState
72    */
73   public function testAddStateInvalidIdException() {
74     $this->setExpectedException(\InvalidArgumentException::class, "The state ID 'draft-draft' must contain only lowercase letters, numbers, and underscores");
75     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
76     $workflow->addState('draft-draft', 'Draft');
77   }
78
79   /**
80    * @covers ::getStates
81    */
82   public function testGetStates() {
83     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
84
85     // Getting states works when there are none.
86     $this->assertArrayEquals([], array_keys($workflow->getStates()));
87     $this->assertArrayEquals([], array_keys($workflow->getStates([])));
88
89     $workflow
90       ->addState('draft', 'Draft')
91       ->addState('published', 'Published')
92       ->addState('archived', 'Archived');
93
94     // States are stored in alphabetical key order.
95     $this->assertArrayEquals([
96       'archived',
97       'draft',
98       'published',
99     ], array_keys($workflow->get('states')));
100
101     // Ensure we're returning state objects.
102     $this->assertInstanceOf(State::class, $workflow->getStates()['draft']);
103
104     // Passing in no IDs returns all states.
105     $this->assertArrayEquals(['draft', 'published', 'archived'], array_keys($workflow->getStates()));
106
107     // The order of states is by weight.
108     $workflow->setStateWeight('published', -1);
109     $this->assertArrayEquals(['published', 'draft', 'archived'], array_keys($workflow->getStates()));
110
111     // The label is also used for sorting if weights are equal.
112     $workflow->setStateWeight('archived', 0);
113     $this->assertArrayEquals(['published', 'archived', 'draft'], array_keys($workflow->getStates()));
114
115     // You can limit the states returned by passing in states IDs.
116     $this->assertArrayEquals(['archived', 'draft'], array_keys($workflow->getStates(['draft', 'archived'])));
117
118     // An empty array does not load all states.
119     $this->assertArrayEquals([], array_keys($workflow->getStates([])));
120   }
121
122   /**
123    * @covers ::getStates
124    */
125   public function testGetStatesException() {
126     $this->setExpectedException(\InvalidArgumentException::class, "The state 'state_that_does_not_exist' does not exist in workflow 'test'");
127     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
128     $workflow->getStates(['state_that_does_not_exist']);
129   }
130
131   /**
132    * @covers ::getState
133    */
134   public function testGetState() {
135     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
136     // By default states are ordered in the order added.
137     $workflow
138       ->addState('draft', 'Draft')
139       ->addState('published', 'Published')
140       ->addState('archived', 'Archived')
141       ->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
142       ->addTransition('publish', 'Publish', ['draft'], 'published');
143
144     // Ensure we're returning state objects and they are set up correctly
145     $this->assertInstanceOf(State::class, $workflow->getState('draft'));
146     $this->assertEquals('archived', $workflow->getState('archived')->id());
147     $this->assertEquals('Archived', $workflow->getState('archived')->label());
148
149     $draft = $workflow->getState('draft');
150     $this->assertTrue($draft->canTransitionTo('draft'));
151     $this->assertTrue($draft->canTransitionTo('published'));
152     $this->assertFalse($draft->canTransitionTo('archived'));
153     $this->assertEquals('Publish', $draft->getTransitionTo('published')->label());
154     $this->assertEquals(0, $draft->weight());
155     $this->assertEquals(1, $workflow->getState('published')->weight());
156     $this->assertEquals(2, $workflow->getState('archived')->weight());
157   }
158
159   /**
160    * @covers ::getState
161    */
162   public function testGetStateException() {
163     $this->setExpectedException(\InvalidArgumentException::class, "The state 'state_that_does_not_exist' does not exist in workflow 'test'");
164     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
165     $workflow->getState('state_that_does_not_exist');
166   }
167
168   /**
169    * @covers ::setStateLabel
170    */
171   public function testSetStateLabel() {
172     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
173     $workflow->addState('draft', 'Draft');
174     $this->assertEquals('Draft', $workflow->getState('draft')->label());
175     $workflow->setStateLabel('draft', 'Unpublished');
176     $this->assertEquals('Unpublished', $workflow->getState('draft')->label());
177   }
178
179   /**
180    * @covers ::setStateLabel
181    */
182   public function testSetStateLabelException() {
183     $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow 'test'");
184     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
185     $workflow->setStateLabel('draft', 'Draft');
186   }
187
188   /**
189    * @covers ::setStateWeight
190    */
191   public function testSetStateWeight() {
192     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
193     $workflow->addState('draft', 'Draft');
194     $this->assertEquals(0, $workflow->getState('draft')->weight());
195     $workflow->setStateWeight('draft', -10);
196     $this->assertEquals(-10, $workflow->getState('draft')->weight());
197   }
198
199   /**
200    * @covers ::setStateWeight
201    */
202   public function testSetStateWeightException() {
203     $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow 'test'");
204     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
205     $workflow->setStateWeight('draft', 10);
206   }
207
208   /**
209    * @covers ::deleteState
210    */
211   public function testDeleteState() {
212     // Create a container so that the plugin manager and workflow type can be
213     // mocked and test that
214     // \Drupal\workflows\WorkflowTypeInterface::deleteState() is called
215     // correctly.
216     $container = new ContainerBuilder();
217     $workflow_type = $this->prophesize(WorkflowTypeInterface::class);
218     $workflow_type->decorateState(Argument::any())->willReturnArgument(0);
219     $workflow_type->decorateTransition(Argument::any())->willReturnArgument(0);
220     $workflow_type->deleteState('draft')->shouldBeCalled();
221     $workflow_type->deleteTransition('create_new_draft')->shouldBeCalled();
222     $workflow_manager = $this->prophesize(WorkflowTypeManager::class);
223     $workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal());
224     $container->set('plugin.manager.workflows.type', $workflow_manager->reveal());
225     \Drupal::setContainer($container);
226
227     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
228     $workflow
229       ->addState('draft', 'Draft')
230       ->addState('published', 'Published')
231       ->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
232       ->addTransition('create_new_draft', 'Create new draft', ['draft', 'published'], 'draft');
233     $this->assertCount(2, $workflow->getStates());
234     $this->assertCount(2, $workflow->getState('published')->getTransitions());
235     $workflow->deleteState('draft');
236     $this->assertFalse($workflow->hasState('draft'));
237     $this->assertCount(1, $workflow->getStates());
238     $this->assertCount(1, $workflow->getState('published')->getTransitions());
239   }
240
241   /**
242    * @covers ::deleteState
243    */
244   public function testDeleteStateException() {
245     $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow 'test'");
246     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
247     $workflow->deleteState('draft');
248   }
249
250   /**
251    * @covers ::deleteState
252    */
253   public function testDeleteOnlyStateException() {
254     $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' can not be deleted from workflow 'test' as it is the only state");
255     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
256     $workflow->addState('draft', 'Draft');
257     $workflow->deleteState('draft');
258   }
259
260   /**
261    * @covers ::addTransition
262    * @covers ::hasTransition
263    */
264   public function testAddTransition() {
265     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
266
267     // By default states are ordered in the order added.
268     $workflow
269       ->addState('draft', 'Draft')
270       ->addState('published', 'Published');
271
272     $this->assertFalse($workflow->getState('draft')->canTransitionTo('published'));
273     $workflow->addTransition('publish', 'Publish', ['draft'], 'published');
274     $this->assertTrue($workflow->getState('draft')->canTransitionTo('published'));
275     $this->assertEquals(0, $workflow->getTransition('publish')->weight());
276     $this->assertTrue($workflow->hasTransition('publish'));
277     $this->assertFalse($workflow->hasTransition('draft'));
278
279     $workflow->addTransition('save_publish', 'Save', ['published'], 'published');
280     $this->assertEquals(1, $workflow->getTransition('save_publish')->weight());
281   }
282
283   /**
284    * @covers ::addTransition
285    */
286   public function testAddTransitionDuplicateException() {
287     $this->setExpectedException(\InvalidArgumentException::class, "The transition 'publish' already exists in workflow 'test'");
288     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
289     $workflow->addState('published', 'Published');
290     $workflow->addTransition('publish', 'Publish', ['published'], 'published');
291     $workflow->addTransition('publish', 'Publish', ['published'], 'published');
292   }
293
294   /**
295    * @covers ::addTransition
296    */
297   public function testAddTransitionInvalidIdException() {
298     $this->setExpectedException(\InvalidArgumentException::class, "The transition ID 'publish-publish' must contain only lowercase letters, numbers, and underscores");
299     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
300     $workflow->addState('published', 'Published');
301     $workflow->addTransition('publish-publish', 'Publish', ['published'], 'published');
302   }
303
304   /**
305    * @covers ::addTransition
306    */
307   public function testAddTransitionMissingFromException() {
308     $this->setExpectedException(\InvalidArgumentException::class, "The state 'draft' does not exist in workflow 'test'");
309     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
310     $workflow->addState('published', 'Published');
311     $workflow->addTransition('publish', 'Publish', ['draft'], 'published');
312   }
313
314   /**
315    * @covers ::addTransition
316    */
317   public function testAddTransitionDuplicateTransitionStatesException() {
318     $this->setExpectedException(\InvalidArgumentException::class, "The 'publish' transition already allows 'draft' to 'published' transitions in workflow 'test'");
319     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
320     $workflow
321       ->addState('draft', 'Draft')
322       ->addState('published', 'Published');
323     $workflow->addTransition('publish', 'Publish', ['draft', 'published'], 'published');
324     $workflow->addTransition('draft_to_published', 'Publish a draft', ['draft'], 'published');
325   }
326
327   /**
328    * @covers ::addTransition
329    */
330   public function testAddTransitionConsistentAfterFromCatch() {
331     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
332     $workflow->addState('published', 'Published');
333     try {
334       $workflow->addTransition('publish', 'Publish', ['draft'], 'published');
335     }
336     catch (\InvalidArgumentException $e) {
337     }
338     // Ensure that the workflow is not left in an inconsistent state after an
339     // exception is thrown from Workflow::setTransitionFromStates() whilst
340     // calling Workflow::addTransition().
341     $this->assertFalse($workflow->hasTransition('publish'));
342   }
343
344   /**
345    * @covers ::addTransition
346    */
347   public function testAddTransitionMissingToException() {
348     $this->setExpectedException(\InvalidArgumentException::class, "The state 'published' does not exist in workflow 'test'");
349     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
350     $workflow->addState('draft', 'Draft');
351     $workflow->addTransition('publish', 'Publish', ['draft'], 'published');
352   }
353
354   /**
355    * @covers ::getTransitions
356    * @covers ::setTransitionWeight
357    */
358   public function testGetTransitions() {
359     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
360
361     // Getting transitions works when there are none.
362     $this->assertArrayEquals([], array_keys($workflow->getTransitions()));
363     $this->assertArrayEquals([], array_keys($workflow->getTransitions([])));
364
365     // By default states are ordered in the order added.
366     $workflow
367       ->addState('a', 'A')
368       ->addState('b', 'B')
369       ->addTransition('a_b', 'A to B', ['a'], 'b')
370       ->addTransition('a_a', 'A to A', ['a'], 'a');
371
372     // Transitions are stored in alphabetical key order in configuration.
373     $this->assertArrayEquals(['a_a', 'a_b'], array_keys($workflow->get('transitions')));
374
375     // Ensure we're returning transition objects.
376     $this->assertInstanceOf(Transition::class, $workflow->getTransitions()['a_a']);
377
378     // Passing in no IDs returns all transitions.
379     $this->assertArrayEquals(['a_b', 'a_a'], array_keys($workflow->getTransitions()));
380
381     // The order of states is by weight.
382     $workflow->setTransitionWeight('a_a', -1);
383     $this->assertArrayEquals(['a_a', 'a_b'], array_keys($workflow->getTransitions()));
384
385     // If all weights are equal it will fallback to labels.
386     $workflow->setTransitionWeight('a_a', 0);
387     $this->assertArrayEquals(['a_a', 'a_b'], array_keys($workflow->getTransitions()));
388     $workflow->setTransitionLabel('a_b', 'A B');
389     $this->assertArrayEquals(['a_b', 'a_a'], array_keys($workflow->getTransitions()));
390
391     // You can limit the states returned by passing in states IDs.
392     $this->assertArrayEquals(['a_a'], array_keys($workflow->getTransitions(['a_a'])));
393
394     // An empty array does not load all states.
395     $this->assertArrayEquals([], array_keys($workflow->getTransitions([])));
396   }
397
398
399   /**
400    * @covers ::getTransition
401    */
402   public function testGetTransition() {
403     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
404     // By default states are ordered in the order added.
405     $workflow
406       ->addState('draft', 'Draft')
407       ->addState('published', 'Published')
408       ->addState('archived', 'Archived')
409       ->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
410       ->addTransition('publish', 'Publish', ['draft'], 'published');
411
412     // Ensure we're returning state objects and they are set up correctly
413     $this->assertInstanceOf(Transition::class, $workflow->getTransition('create_new_draft'));
414     $this->assertEquals('publish', $workflow->getTransition('publish')->id());
415     $this->assertEquals('Publish', $workflow->getTransition('publish')->label());
416
417     $transition = $workflow->getTransition('publish');
418     $this->assertEquals($workflow->getState('draft'), $transition->from()['draft']);
419     $this->assertEquals($workflow->getState('published'), $transition->to());
420   }
421
422   /**
423    * @covers ::getTransition
424    */
425   public function testGetTransitionException() {
426     $this->setExpectedException(\InvalidArgumentException::class, "The transition 'transition_that_does_not_exist' does not exist in workflow 'test'");
427     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
428     $workflow->getTransition('transition_that_does_not_exist');
429   }
430
431   /**
432    * @covers ::getTransitionsForState
433    */
434   public function testGetTransitionsForState() {
435     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
436     // By default states are ordered in the order added.
437     $workflow
438       ->addState('draft', 'Draft')
439       ->addState('published', 'Published')
440       ->addState('archived', 'Archived')
441       ->addTransition('create_new_draft', 'Create new draft', ['archived', 'draft'], 'draft')
442       ->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
443       ->addTransition('archive', 'Archive', ['published'], 'archived');
444
445     $this->assertEquals(['create_new_draft', 'publish'], array_keys($workflow->getTransitionsForState('draft')));
446     $this->assertEquals(['create_new_draft'], array_keys($workflow->getTransitionsForState('draft', 'to')));
447     $this->assertEquals(['publish', 'archive'], array_keys($workflow->getTransitionsForState('published')));
448     $this->assertEquals(['publish'], array_keys($workflow->getTransitionsForState('published', 'to')));
449     $this->assertEquals(['create_new_draft'], array_keys($workflow->getTransitionsForState('archived', 'from')));
450     $this->assertEquals(['archive'], array_keys($workflow->getTransitionsForState('archived', 'to')));
451   }
452
453
454   /**
455    * @covers ::getTransitionFromStateToState
456    * @covers ::hasTransitionFromStateToState
457    */
458   public function testGetTransitionFromStateToState() {
459     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
460     // By default states are ordered in the order added.
461     $workflow
462       ->addState('draft', 'Draft')
463       ->addState('published', 'Published')
464       ->addState('archived', 'Archived')
465       ->addTransition('create_new_draft', 'Create new draft', ['archived', 'draft'], 'draft')
466       ->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
467       ->addTransition('archive', 'Archive', ['published'], 'archived');
468
469     $this->assertTrue($workflow->hasTransitionFromStateToState('draft', 'published'));
470     $this->assertFalse($workflow->hasTransitionFromStateToState('archived', 'archived'));
471     $transition = $workflow->getTransitionFromStateToState('published', 'archived');
472     $this->assertEquals('Archive', $transition->label());
473   }
474
475   /**
476    * @covers ::getTransitionFromStateToState
477    */
478   public function testGetTransitionFromStateToStateException() {
479     $this->setExpectedException(\InvalidArgumentException::class, "The transition from 'archived' to 'archived' does not exist in workflow 'test'");
480     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
481     // By default states are ordered in the order added.
482     $workflow
483       ->addState('draft', 'Draft')
484       ->addState('published', 'Published')
485       ->addState('archived', 'Archived')
486       ->addTransition('create_new_draft', 'Create new draft', ['archived', 'draft'], 'draft')
487       ->addTransition('publish', 'Publish', ['draft', 'published'], 'published')
488       ->addTransition('archive', 'Archive', ['published'], 'archived');
489
490     $workflow->getTransitionFromStateToState('archived', 'archived');
491   }
492
493   /**
494    * @covers ::setTransitionLabel
495    */
496   public function testSetTransitionLabel() {
497     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
498     $workflow
499       ->addState('draft', 'Draft')
500       ->addState('published', 'Published')
501       ->addTransition('publish', 'Publish', ['draft'], 'published');
502     $this->assertEquals('Publish', $workflow->getTransition('publish')->label());
503     $workflow->setTransitionLabel('publish', 'Publish!');
504     $this->assertEquals('Publish!', $workflow->getTransition('publish')->label());
505   }
506
507   /**
508    * @covers ::setTransitionLabel
509    */
510   public function testSetTransitionLabelException() {
511     $this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow 'test'");
512     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
513     $workflow->addState('published', 'Published');
514     $workflow->setTransitionLabel('draft-published', 'Publish');
515   }
516
517   /**
518    * @covers ::setTransitionWeight
519    */
520   public function testSetTransitionWeight() {
521     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
522     $workflow
523       ->addState('draft', 'Draft')
524       ->addState('published', 'Published')
525       ->addTransition('publish', 'Publish', ['draft'], 'published');
526     $this->assertEquals(0, $workflow->getTransition('publish')->weight());
527     $workflow->setTransitionWeight('publish', 10);
528     $this->assertEquals(10, $workflow->getTransition('publish')->weight());
529   }
530
531   /**
532    * @covers ::setTransitionWeight
533    */
534   public function testSetTransitionWeightException() {
535     $this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow 'test'");
536     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
537     $workflow->addState('published', 'Published');
538     $workflow->setTransitionWeight('draft-published', 10);
539   }
540
541   /**
542    * @covers ::setTransitionFromStates
543    */
544   public function testSetTransitionFromStates() {
545     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
546     $workflow
547       ->addState('draft', 'Draft')
548       ->addState('published', 'Published')
549       ->addState('archived', 'Archived')
550       ->addTransition('test', 'Test', ['draft'], 'draft');
551
552     $this->assertTrue($workflow->hasTransitionFromStateToState('draft', 'draft'));
553     $this->assertFalse($workflow->hasTransitionFromStateToState('published', 'draft'));
554     $this->assertFalse($workflow->hasTransitionFromStateToState('archived', 'draft'));
555     $workflow->setTransitionFromStates('test', ['draft', 'published', 'archived']);
556     $this->assertTrue($workflow->hasTransitionFromStateToState('draft', 'draft'));
557     $this->assertTrue($workflow->hasTransitionFromStateToState('published', 'draft'));
558     $this->assertTrue($workflow->hasTransitionFromStateToState('archived', 'draft'));
559     $workflow->setTransitionFromStates('test', ['published', 'archived']);
560     $this->assertFalse($workflow->hasTransitionFromStateToState('draft', 'draft'));
561     $this->assertTrue($workflow->hasTransitionFromStateToState('published', 'draft'));
562     $this->assertTrue($workflow->hasTransitionFromStateToState('archived', 'draft'));
563   }
564
565   /**
566    * @covers ::setTransitionFromStates
567    */
568   public function testSetTransitionFromStatesMissingTransition() {
569     $this->setExpectedException(\InvalidArgumentException::class, "The transition 'test' does not exist in workflow 'test'");
570     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
571     $workflow
572       ->addState('draft', 'Draft')
573       ->addState('published', 'Published')
574       ->addState('archived', 'Archived')
575       ->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft');
576
577     $workflow->setTransitionFromStates('test', ['draft', 'published', 'archived']);
578   }
579
580   /**
581    * @covers ::setTransitionFromStates
582    */
583   public function testSetTransitionFromStatesMissingState() {
584     $this->setExpectedException(\InvalidArgumentException::class, "The state 'published' does not exist in workflow 'test'");
585     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
586     $workflow
587       ->addState('draft', 'Draft')
588       ->addState('archived', 'Archived')
589       ->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft');
590
591     $workflow->setTransitionFromStates('create_new_draft', ['draft', 'published', 'archived']);
592   }
593
594   /**
595    * @covers ::setTransitionFromStates
596    */
597   public function testSetTransitionFromStatesAlreadyExists() {
598     $this->setExpectedException(\InvalidArgumentException::class, "The 'create_new_draft' transition already allows 'draft' to 'draft' transitions in workflow 'test'");
599     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
600     $workflow
601       ->addState('draft', 'Draft')
602       ->addState('archived', 'Archived')
603       ->addState('needs_review', 'Needs Review')
604       ->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
605       ->addTransition('needs_review', 'Needs review', ['needs_review'], 'draft');
606
607     $workflow->setTransitionFromStates('needs_review', ['draft']);
608   }
609
610   /**
611    * @covers ::deleteTransition
612    */
613   public function testDeleteTransition() {
614     // Create a container so that the plugin manager and workflow type can be
615     // mocked and test that
616     // \Drupal\workflows\WorkflowTypeInterface::deleteState() is called
617     // correctly.
618     $container = new ContainerBuilder();
619     $workflow_type = $this->prophesize(WorkflowTypeInterface::class);
620     $workflow_type->decorateState(Argument::any())->willReturnArgument(0);
621     $workflow_type->decorateTransition(Argument::any())->willReturnArgument(0);
622     $workflow_type->deleteTransition('publish')->shouldBeCalled();
623     $workflow_manager = $this->prophesize(WorkflowTypeManager::class);
624     $workflow_manager->createInstance('test_type', Argument::any())->willReturn($workflow_type->reveal());
625     $container->set('plugin.manager.workflows.type', $workflow_manager->reveal());
626     \Drupal::setContainer($container);
627
628     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
629     $workflow
630       ->addState('draft', 'Draft')
631       ->addState('published', 'Published')
632       ->addTransition('create_new_draft', 'Create new draft', ['draft'], 'draft')
633       ->addTransition('publish', 'Publish', ['draft'], 'published');
634     $this->assertTrue($workflow->getState('draft')->canTransitionTo('published'));
635     $workflow->deleteTransition('publish');
636     $this->assertFalse($workflow->getState('draft')->canTransitionTo('published'));
637     $this->assertTrue($workflow->getState('draft')->canTransitionTo('draft'));
638   }
639
640   /**
641    * @covers ::deleteTransition
642    */
643   public function testDeleteTransitionException() {
644     $this->setExpectedException(\InvalidArgumentException::class, "The transition 'draft-published' does not exist in workflow 'test'");
645     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
646     $workflow->addState('published', 'Published');
647     $workflow->deleteTransition('draft-published');
648   }
649
650   /**
651    * @covers ::status
652    */
653   public function testStatus() {
654     $workflow = new Workflow(['id' => 'test', 'type' => 'test_type'], 'workflow');
655     $this->assertFalse($workflow->status());
656     $workflow->addState('published', 'Published');
657     $this->assertTrue($workflow->status());
658   }
659
660 }