Pull merge.
[yaffs-website] / web / core / modules / content_moderation / tests / src / Kernel / ContentModerationStateTest.php
1 <?php
2
3 namespace Drupal\Tests\content_moderation\Kernel;
4
5 use Drupal\content_moderation\Entity\ContentModerationState;
6 use Drupal\Core\Entity\EntityInterface;
7 use Drupal\Core\Entity\EntityPublishedInterface;
8 use Drupal\Core\Entity\EntityStorageException;
9 use Drupal\Core\Language\LanguageInterface;
10 use Drupal\entity_test\Entity\EntityTestRev;
11 use Drupal\KernelTests\KernelTestBase;
12 use Drupal\language\Entity\ConfigurableLanguage;
13 use Drupal\node\Entity\Node;
14 use Drupal\node\Entity\NodeType;
15 use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
16 use Drupal\workflows\Entity\Workflow;
17
18 /**
19  * Tests links between a content entity and a content_moderation_state entity.
20  *
21  * @group content_moderation
22  */
23 class ContentModerationStateTest extends KernelTestBase {
24
25   use ContentModerationTestTrait;
26
27   /**
28    * {@inheritdoc}
29    */
30   public static $modules = [
31     'entity_test',
32     'node',
33     'block',
34     'block_content',
35     'media',
36     'media_test_source',
37     'image',
38     'file',
39     'field',
40     'content_moderation',
41     'user',
42     'system',
43     'language',
44     'content_translation',
45     'text',
46     'workflows',
47   ];
48
49   /**
50    * @var \Drupal\Core\Entity\EntityTypeManager
51    */
52   protected $entityTypeManager;
53
54   /**
55    * {@inheritdoc}
56    */
57   protected function setUp() {
58     parent::setUp();
59
60     $this->installSchema('node', 'node_access');
61     $this->installEntitySchema('node');
62     $this->installEntitySchema('user');
63     $this->installEntitySchema('entity_test_rev');
64     $this->installEntitySchema('entity_test_no_bundle');
65     $this->installEntitySchema('entity_test_mulrevpub');
66     $this->installEntitySchema('block_content');
67     $this->installEntitySchema('media');
68     $this->installEntitySchema('file');
69     $this->installEntitySchema('content_moderation_state');
70     $this->installConfig('content_moderation');
71     $this->installSchema('file', 'file_usage');
72     $this->installConfig(['field', 'system', 'image', 'file', 'media']);
73
74     $this->entityTypeManager = $this->container->get('entity_type.manager');
75   }
76
77   /**
78    * Tests basic monolingual content moderation through the API.
79    *
80    * @dataProvider basicModerationTestCases
81    */
82   public function testBasicModeration($entity_type_id) {
83     $entity = $this->createEntity($entity_type_id);
84     if ($entity instanceof EntityPublishedInterface) {
85       $entity->setUnpublished();
86     }
87     $entity->save();
88     $entity = $this->reloadEntity($entity);
89     $this->assertEquals('draft', $entity->moderation_state->value);
90
91     $entity->moderation_state->value = 'published';
92     $entity->save();
93
94     $entity = $this->reloadEntity($entity);
95     $this->assertEquals('published', $entity->moderation_state->value);
96
97     // Change the state without saving the node.
98     $content_moderation_state = ContentModerationState::load(1);
99     $content_moderation_state->set('moderation_state', 'draft');
100     $content_moderation_state->setNewRevision(TRUE);
101     $content_moderation_state->save();
102
103     $entity = $this->reloadEntity($entity, 3);
104     $this->assertEquals('draft', $entity->moderation_state->value);
105     if ($entity instanceof EntityPublishedInterface) {
106       $this->assertFalse($entity->isPublished());
107     }
108
109     // Get the default revision.
110     $entity = $this->reloadEntity($entity);
111     if ($entity instanceof EntityPublishedInterface) {
112       $this->assertTrue((bool) $entity->isPublished());
113     }
114     $this->assertEquals(2, $entity->getRevisionId());
115
116     $entity->moderation_state->value = 'published';
117     $entity->save();
118
119     $entity = $this->reloadEntity($entity, 4);
120     $this->assertEquals('published', $entity->moderation_state->value);
121
122     // Get the default revision.
123     $entity = $this->reloadEntity($entity);
124     if ($entity instanceof EntityPublishedInterface) {
125       $this->assertTrue((bool) $entity->isPublished());
126     }
127     $this->assertEquals(4, $entity->getRevisionId());
128
129     // Update the node to archived which will then be the default revision.
130     $entity->moderation_state->value = 'archived';
131     $entity->save();
132
133     // Revert to the previous (published) revision.
134     $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
135     $previous_revision = $entity_storage->loadRevision(4);
136     $previous_revision->isDefaultRevision(TRUE);
137     $previous_revision->setNewRevision(TRUE);
138     $previous_revision->save();
139
140     // Get the default revision.
141     $entity = $this->reloadEntity($entity);
142     $this->assertEquals('published', $entity->moderation_state->value);
143     if ($entity instanceof EntityPublishedInterface) {
144       $this->assertTrue($entity->isPublished());
145     }
146
147     // Set an invalid moderation state.
148     $this->setExpectedException(EntityStorageException::class);
149     $entity->moderation_state->value = 'foobar';
150     $entity->save();
151   }
152
153   /**
154    * Test cases for basic moderation test.
155    */
156   public function basicModerationTestCases() {
157     return [
158       'Nodes' => [
159         'node',
160       ],
161       'Block content' => [
162         'block_content',
163       ],
164       'Media' => [
165         'media',
166       ],
167       'Test entity - revisions, data table, and published interface' => [
168         'entity_test_mulrevpub',
169       ],
170       'Entity Test with revisions' => [
171         'entity_test_rev',
172       ],
173       'Entity without bundle' => [
174         'entity_test_no_bundle',
175       ],
176     ];
177   }
178
179   /**
180    * Tests removal of content moderation state entity.
181    *
182    * @dataProvider basicModerationTestCases
183    */
184   public function testContentModerationStateDataRemoval($entity_type_id) {
185     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
186     $entity = $this->createEntity($entity_type_id);
187     $entity->save();
188     $entity = $this->reloadEntity($entity);
189     $entity->delete();
190     $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
191     $this->assertFalse($content_moderation_state);
192   }
193
194   /**
195    * Tests removal of content moderation state entity revisions.
196    *
197    * @dataProvider basicModerationTestCases
198    */
199   public function testContentModerationStateRevisionDataRemoval($entity_type_id) {
200     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
201     $entity = $this->createEntity($entity_type_id);
202     $entity->save();
203     $revision = clone $entity;
204     $revision->isDefaultRevision(FALSE);
205     $content_moderation_state = ContentModerationState::loadFromModeratedEntity($revision);
206     $this->assertTrue($content_moderation_state);
207     $entity = $this->reloadEntity($entity);
208     $entity->setNewRevision(TRUE);
209     $entity->save();
210     $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
211     $entity_storage->deleteRevision($revision->getRevisionId());
212     $content_moderation_state = ContentModerationState::loadFromModeratedEntity($revision);
213     $this->assertFalse($content_moderation_state);
214     $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
215     $this->assertTrue($content_moderation_state);
216   }
217
218   /**
219    * Tests removal of content moderation state pending entity revisions.
220    *
221    * @dataProvider basicModerationTestCases
222    */
223   public function testContentModerationStatePendingRevisionDataRemoval($entity_type_id) {
224     $entity = $this->createEntity($entity_type_id);
225     $entity->moderation_state = 'published';
226     $entity->save();
227     $entity->setNewRevision(TRUE);
228     $entity->moderation_state = 'draft';
229     $entity->save();
230
231     $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
232     $this->assertTrue($content_moderation_state);
233
234     $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
235     $entity_storage->deleteRevision($entity->getRevisionId());
236
237     $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
238     $this->assertFalse($content_moderation_state);
239   }
240
241   /**
242    * Tests removal of content moderation state translations.
243    *
244    * @dataProvider basicModerationTestCases
245    */
246   public function testContentModerationStateTranslationDataRemoval($entity_type_id) {
247     // Test content moderation state translation deletion.
248     if ($this->entityTypeManager->getDefinition($entity_type_id)->isTranslatable()) {
249       /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
250       $entity = $this->createEntity($entity_type_id);
251       $langcode = 'it';
252       ConfigurableLanguage::createFromLangcode($langcode)
253         ->save();
254       $entity->save();
255       $translation = $entity->addTranslation($langcode, ['title' => 'Titolo test']);
256       // Make sure we add values for all of the required fields.
257       if ($entity_type_id == 'block_content') {
258         $translation->info = $this->randomString();
259       }
260       $translation->save();
261       $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
262       $this->assertTrue($content_moderation_state->hasTranslation($langcode));
263       $entity->removeTranslation($langcode);
264       $entity->save();
265       $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
266       $this->assertFalse($content_moderation_state->hasTranslation($langcode));
267     }
268   }
269
270   /**
271    * Tests basic multilingual content moderation through the API.
272    */
273   public function testMultilingualModeration() {
274     // Enable French.
275     ConfigurableLanguage::createFromLangcode('fr')->save();
276     $node_type = NodeType::create([
277       'type' => 'example',
278     ]);
279     $node_type->save();
280
281     $workflow = $this->createEditorialWorkflow();
282     $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
283     $workflow->save();
284
285     $english_node = Node::create([
286       'type' => 'example',
287       'title' => 'Test title',
288     ]);
289     // Revision 1 (en).
290     $english_node
291       ->setUnpublished()
292       ->save();
293     $this->assertEquals('draft', $english_node->moderation_state->value);
294     $this->assertFalse($english_node->isPublished());
295
296     // Create a French translation.
297     $french_node = $english_node->addTranslation('fr', ['title' => 'French title']);
298     $french_node->setUnpublished();
299     // Revision 2 (fr).
300     $french_node->save();
301     $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
302     $this->assertEquals('draft', $french_node->moderation_state->value);
303     $this->assertFalse($french_node->isPublished());
304
305     // Move English node to create another draft.
306     $english_node = $this->reloadEntity($english_node);
307     $english_node->moderation_state->value = 'draft';
308     // Revision 3 (en, fr).
309     $english_node->save();
310     $english_node = $this->reloadEntity($english_node);
311     $this->assertEquals('draft', $english_node->moderation_state->value);
312
313     // French node should still be in draft.
314     $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
315     $this->assertEquals('draft', $french_node->moderation_state->value);
316
317     // Publish the French node.
318     $french_node->moderation_state->value = 'published';
319     // Revision 4 (en, fr).
320     $french_node->save();
321     $french_node = $this->reloadEntity($french_node)->getTranslation('fr');
322     $this->assertTrue($french_node->isPublished());
323     $this->assertEquals('published', $french_node->moderation_state->value);
324     $this->assertTrue($french_node->isPublished());
325     $english_node = $french_node->getTranslation('en');
326     $this->assertEquals('draft', $english_node->moderation_state->value);
327
328     // Publish the English node.
329     $english_node->moderation_state->value = 'published';
330     // Revision 5 (en, fr).
331     $english_node->save();
332     $english_node = $this->reloadEntity($english_node);
333     $this->assertTrue($english_node->isPublished());
334
335     // Move the French node back to draft.
336     $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
337     $this->assertTrue($french_node->isPublished());
338     $french_node->moderation_state->value = 'draft';
339     // Revision 6 (en, fr).
340     $french_node->save();
341     $french_node = $this->reloadEntity($english_node, 6)->getTranslation('fr');
342     $this->assertFalse($french_node->isPublished());
343     $this->assertTrue($french_node->getTranslation('en')->isPublished());
344
345     // Republish the French node.
346     $french_node->moderation_state->value = 'published';
347     // Revision 7 (en, fr).
348     $french_node->save();
349     $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
350     $this->assertTrue($french_node->isPublished());
351
352     // Change the EN state without saving the node.
353     $content_moderation_state = ContentModerationState::load(1);
354     $content_moderation_state->set('moderation_state', 'draft');
355     $content_moderation_state->setNewRevision(TRUE);
356     // Revision 8 (en, fr).
357     $content_moderation_state->save();
358     $english_node = $this->reloadEntity($french_node, $french_node->getRevisionId() + 1);
359
360     $this->assertEquals('draft', $english_node->moderation_state->value);
361     $french_node = $this->reloadEntity($english_node)->getTranslation('fr');
362     $this->assertEquals('published', $french_node->moderation_state->value);
363
364     // This should unpublish the French node.
365     $content_moderation_state = ContentModerationState::load(1);
366     $content_moderation_state = $content_moderation_state->getTranslation('fr');
367     $content_moderation_state->set('moderation_state', 'draft');
368     $content_moderation_state->setNewRevision(TRUE);
369     // Revision 9 (en, fr).
370     $content_moderation_state->save();
371
372     $english_node = $this->reloadEntity($english_node, $english_node->getRevisionId());
373     $this->assertEquals('draft', $english_node->moderation_state->value);
374     $french_node = $this->reloadEntity($english_node, '9')->getTranslation('fr');
375     $this->assertEquals('draft', $french_node->moderation_state->value);
376     // Switching the moderation state to an unpublished state should update the
377     // entity.
378     $this->assertFalse($french_node->isPublished());
379
380     // Get the default english node.
381     $english_node = $this->reloadEntity($english_node);
382     $this->assertTrue($english_node->isPublished());
383     $this->assertEquals(7, $english_node->getRevisionId());
384   }
385
386   /**
387    * Tests moderation when the moderation_state field has a config override.
388    */
389   public function testModerationWithFieldConfigOverride() {
390     NodeType::create([
391       'type' => 'test_type',
392     ])->save();
393
394     $workflow = $this->createEditorialWorkflow();
395     $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'test_type');
396     $workflow->save();
397
398     $fields = $this->container->get('entity_field.manager')->getFieldDefinitions('node', 'test_type');
399     $field_config = $fields['moderation_state']->getConfig('test_type');
400     $field_config->setLabel('Field Override!');
401     $field_config->save();
402
403     $node = Node::create([
404       'title' => 'Test node',
405       'type' => 'test_type',
406     ]);
407     $node->save();
408     $this->assertFalse($node->isPublished());
409     $this->assertEquals('draft', $node->moderation_state->value);
410
411     $node->moderation_state = 'published';
412     $node->save();
413     $this->assertTrue($node->isPublished());
414     $this->assertEquals('published', $node->moderation_state->value);
415   }
416
417   /**
418    * Tests that entities with special languages can be moderated.
419    *
420    * @dataProvider moderationWithSpecialLanguagesTestCases
421    */
422   public function testModerationWithSpecialLanguages($original_language, $updated_language) {
423     $workflow = $this->createEditorialWorkflow();
424     $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
425     $workflow->save();
426
427     // Create a test entity.
428     $entity = EntityTestRev::create([
429       'langcode' => $original_language,
430     ]);
431     $entity->save();
432     $this->assertEquals('draft', $entity->moderation_state->value);
433
434     $entity->moderation_state->value = 'published';
435     $entity->langcode = $updated_language;
436     $entity->save();
437
438     $this->assertEquals('published', EntityTestRev::load($entity->id())->moderation_state->value);
439   }
440
441   /**
442    * Test cases for ::testModerationWithSpecialLanguages().
443    */
444   public function moderationWithSpecialLanguagesTestCases() {
445     return [
446       'Not specified to not specified' => [
447         LanguageInterface::LANGCODE_NOT_SPECIFIED,
448         LanguageInterface::LANGCODE_NOT_SPECIFIED,
449       ],
450       'English to not specified' => [
451         'en',
452         LanguageInterface::LANGCODE_NOT_SPECIFIED,
453       ],
454       'Not specified to english' => [
455         LanguageInterface::LANGCODE_NOT_SPECIFIED,
456         'en',
457       ],
458     ];
459   }
460
461   /**
462    * Test changing the language of content without adding a translation.
463    */
464   public function testChangingContentLangcode() {
465     ConfigurableLanguage::createFromLangcode('fr')->save();
466     NodeType::create([
467       'type' => 'test_type',
468     ])->save();
469     $workflow = $this->createEditorialWorkflow();
470     $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'test_type');
471     $workflow->save();
472
473     $entity = Node::create([
474       'title' => 'Test node',
475       'langcode' => 'en',
476       'type' => 'test_type',
477     ]);
478     $entity->save();
479
480     $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
481     $this->assertCount(1, $entity->getTranslationLanguages());
482     $this->assertCount(1, $content_moderation_state->getTranslationLanguages());
483     $this->assertEquals('en', $entity->langcode->value);
484     $this->assertEquals('en', $content_moderation_state->langcode->value);
485
486     $entity->langcode = 'fr';
487     $entity->save();
488
489     $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
490     $this->assertCount(1, $entity->getTranslationLanguages());
491     $this->assertCount(1, $content_moderation_state->getTranslationLanguages());
492     $this->assertEquals('fr', $entity->langcode->value);
493     $this->assertEquals('fr', $content_moderation_state->langcode->value);
494   }
495
496   /**
497    * Tests that a non-translatable entity type with a langcode can be moderated.
498    */
499   public function testNonTranslatableEntityTypeModeration() {
500     $workflow = $this->createEditorialWorkflow();
501     $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
502     $workflow->save();
503
504     // Check that the tested entity type is not translatable.
505     $entity_type = \Drupal::entityTypeManager()->getDefinition('entity_test_rev');
506     $this->assertFalse($entity_type->isTranslatable(), 'The test entity type is not translatable.');
507
508     // Create a test entity.
509     $entity = EntityTestRev::create();
510     $entity->save();
511     $this->assertEquals('draft', $entity->moderation_state->value);
512
513     $entity->moderation_state->value = 'published';
514     $entity->save();
515
516     $this->assertEquals('published', EntityTestRev::load($entity->id())->moderation_state->value);
517   }
518
519   /**
520    * Tests that a non-translatable entity type without a langcode can be
521    * moderated.
522    */
523   public function testNonLangcodeEntityTypeModeration() {
524     // Unset the langcode entity key for 'entity_test_rev'.
525     $entity_type = clone \Drupal::entityTypeManager()->getDefinition('entity_test_rev');
526     $keys = $entity_type->getKeys();
527     unset($keys['langcode']);
528     $entity_type->set('entity_keys', $keys);
529     \Drupal::state()->set('entity_test_rev.entity_type', $entity_type);
530
531     // Update the entity type in order to remove the 'langcode' field.
532     \Drupal::entityDefinitionUpdateManager()->applyUpdates();
533
534     $workflow = $this->createEditorialWorkflow();
535     $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
536     $workflow->save();
537
538     // Check that the tested entity type is not translatable and does not have a
539     // 'langcode' entity key.
540     $entity_type = \Drupal::entityTypeManager()->getDefinition('entity_test_rev');
541     $this->assertFalse($entity_type->isTranslatable(), 'The test entity type is not translatable.');
542     $this->assertFalse($entity_type->getKey('langcode'), "The test entity type does not have a 'langcode' entity key.");
543
544     // Create a test entity.
545     $entity = EntityTestRev::create();
546     $entity->save();
547     $this->assertEquals('draft', $entity->moderation_state->value);
548
549     $entity->moderation_state->value = 'published';
550     $entity->save();
551
552     $this->assertEquals('published', EntityTestRev::load($entity->id())->moderation_state->value);
553   }
554
555   /**
556    * Tests the dependencies of the workflow when using content moderation.
557    */
558   public function testWorkflowDependencies() {
559     $node_type = NodeType::create([
560       'type' => 'example',
561     ]);
562     $node_type->save();
563
564     $workflow = $this->createEditorialWorkflow();
565     // Test both a config and non-config based bundle and entity type.
566     $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'example');
567     $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev');
568     $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_no_bundle', 'entity_test_no_bundle');
569     $workflow->save();
570
571     $this->assertEquals([
572       'module' => [
573         'content_moderation',
574         'entity_test',
575       ],
576       'config' => [
577         'node.type.example',
578       ],
579     ], $workflow->getDependencies());
580
581     $this->assertEquals([
582       'entity_test_no_bundle',
583       'entity_test_rev',
584       'node',
585     ], $workflow->getTypePlugin()->getEntityTypes());
586
587     // Delete the node type and ensure it is removed from the workflow.
588     $node_type->delete();
589     $workflow = Workflow::load('editorial');
590     $entity_types = $workflow->getTypePlugin()->getEntityTypes();
591     $this->assertFalse(in_array('node', $entity_types));
592
593     // Uninstall entity test and ensure it's removed from the workflow.
594     $this->container->get('config.manager')->uninstall('module', 'entity_test');
595     $workflow = Workflow::load('editorial');
596     $entity_types = $workflow->getTypePlugin()->getEntityTypes();
597     $this->assertEquals([], $entity_types);
598   }
599
600   /**
601    * Test the content moderation workflow dependencies for non-config bundles.
602    */
603   public function testWorkflowNonConfigBundleDependencies() {
604     // Create a bundle not based on any particular configuration.
605     entity_test_create_bundle('test_bundle');
606
607     $workflow = $this->createEditorialWorkflow();
608     $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test', 'test_bundle');
609     $workflow->save();
610
611     // Ensure the bundle is correctly added to the workflow.
612     $this->assertEquals([
613       'module' => [
614         'content_moderation',
615         'entity_test',
616       ],
617     ], $workflow->getDependencies());
618     $this->assertEquals([
619       'test_bundle',
620     ], $workflow->getTypePlugin()->getBundlesForEntityType('entity_test'));
621
622     // Delete the test bundle to ensure the workflow entity responds
623     // appropriately.
624     entity_test_delete_bundle('test_bundle');
625
626     $workflow = Workflow::load('editorial');
627     $this->assertEquals([], $workflow->getTypePlugin()->getBundlesForEntityType('entity_test'));
628     $this->assertEquals([
629       'module' => [
630         'content_moderation',
631       ],
632     ], $workflow->getDependencies());
633   }
634
635   /**
636    * Test the revision default state of the moderation state entity revisions.
637    *
638    * @param string $entity_type_id
639    *   The ID of entity type to be tested.
640    *
641    * @dataProvider basicModerationTestCases
642    */
643   public function testRevisionDefaultState($entity_type_id) {
644     // Check that the revision default state of the moderated entity and the
645     // content moderation state entity always match.
646     /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
647     $storage = $this->entityTypeManager->getStorage($entity_type_id);
648     /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $cms_storage */
649     $cms_storage = $this->entityTypeManager->getStorage('content_moderation_state');
650
651     $entity = $this->createEntity($entity_type_id);
652     $entity->get('moderation_state')->value = 'published';
653     $storage->save($entity);
654     /** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
655     $cms_entity = $cms_storage->loadUnchanged(1);
656     $this->assertEquals($entity->getLoadedRevisionId(), $cms_entity->get('content_entity_revision_id')->value);
657
658     $entity->get('moderation_state')->value = 'published';
659     $storage->save($entity);
660     /** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
661     $cms_entity = $cms_storage->loadUnchanged(1);
662     $this->assertEquals($entity->getLoadedRevisionId(), $cms_entity->get('content_entity_revision_id')->value);
663
664     $entity->get('moderation_state')->value = 'draft';
665     $storage->save($entity);
666     /** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
667     $cms_entity = $cms_storage->loadUnchanged(1);
668     $this->assertEquals($entity->getLoadedRevisionId() - 1, $cms_entity->get('content_entity_revision_id')->value);
669
670     $entity->get('moderation_state')->value = 'published';
671     $storage->save($entity);
672     /** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
673     $cms_entity = $cms_storage->loadUnchanged(1);
674     $this->assertEquals($entity->getLoadedRevisionId(), $cms_entity->get('content_entity_revision_id')->value);
675   }
676
677   /**
678    * Creates an entity.
679    *
680    * The entity will have required fields populated and the corresponding bundle
681    * will be enabled for content moderation.
682    *
683    * @param string $entity_type_id
684    *   The entity type ID.
685    *
686    * @return \Drupal\Core\Entity\ContentEntityInterface
687    *   The created entity.
688    */
689   protected function createEntity($entity_type_id) {
690     $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
691
692     $bundle_id = $entity_type_id;
693     // Set up a bundle entity type for the specified entity type, if needed.
694     if ($bundle_entity_type_id = $entity_type->getBundleEntityType()) {
695       $bundle_entity_type = $this->entityTypeManager->getDefinition($bundle_entity_type_id);
696       $bundle_entity_storage = $this->entityTypeManager->getStorage($bundle_entity_type_id);
697
698       $bundle_id = 'example';
699       if (!$bundle_entity_storage->load($bundle_id)) {
700         $bundle_entity = $bundle_entity_storage->create([
701           $bundle_entity_type->getKey('id') => 'example',
702         ]);
703         if ($entity_type_id == 'media') {
704           $bundle_entity->set('source', 'test');
705           $bundle_entity->save();
706           $source_field = $bundle_entity->getSource()->createSourceField($bundle_entity);
707           $source_field->getFieldStorageDefinition()->save();
708           $source_field->save();
709           $bundle_entity->set('source_configuration', [
710             'source_field' => $source_field->getName(),
711           ]);
712         }
713         $bundle_entity->save();
714       }
715     }
716
717     $workflow = $this->createEditorialWorkflow();
718     $workflow->getTypePlugin()->addEntityTypeAndBundle($entity_type_id, $bundle_id);
719     $workflow->save();
720
721     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
722     $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
723     $entity = $entity_storage->create([
724       $entity_type->getKey('label') => 'Test title',
725       $entity_type->getKey('bundle') => $bundle_id,
726     ]);
727     // Make sure we add values for all of the required fields.
728     if ($entity_type_id == 'block_content') {
729       $entity->info = $this->randomString();
730     }
731     return $entity;
732   }
733
734   /**
735    * Reloads the entity after clearing the static cache.
736    *
737    * @param \Drupal\Core\Entity\EntityInterface $entity
738    *   The entity to reload.
739    * @param int|bool $revision_id
740    *   The specific revision ID to load. Defaults FALSE and just loads the
741    *   default revision.
742    *
743    * @return \Drupal\Core\Entity\EntityInterface
744    *   The reloaded entity.
745    */
746   protected function reloadEntity(EntityInterface $entity, $revision_id = FALSE) {
747     $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
748     $storage->resetCache([$entity->id()]);
749     if ($revision_id) {
750       return $storage->loadRevision($revision_id);
751     }
752     return $storage->load($entity->id());
753   }
754
755 }