3 namespace Drupal\Tests\workspaces\Kernel;
5 use Drupal\Core\Entity\EntityStorageException;
6 use Drupal\Core\Form\FormState;
7 use Drupal\entity_test\Entity\EntityTestMulRev;
8 use Drupal\entity_test\Entity\EntityTestMulRevPub;
9 use Drupal\KernelTests\KernelTestBase;
10 use Drupal\system\Form\SiteInformationForm;
11 use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
12 use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
13 use Drupal\Tests\node\Traits\NodeCreationTrait;
14 use Drupal\Tests\user\Traits\UserCreationTrait;
15 use Drupal\views\Tests\ViewResultAssertionTrait;
16 use Drupal\views\Views;
19 * Tests a complete deployment scenario across different workspaces.
23 class WorkspaceIntegrationTest extends KernelTestBase {
25 use ContentTypeCreationTrait;
26 use EntityReferenceTestTrait;
27 use NodeCreationTrait;
28 use UserCreationTrait;
29 use ViewResultAssertionTrait;
30 use WorkspaceTestTrait;
33 * The entity type manager.
35 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
37 protected $entityTypeManager;
40 * Creation timestamp that should be incremented for each new entity.
44 protected $createdTimestamp;
49 protected static $modules = [
63 protected function setUp() {
66 $this->entityTypeManager = \Drupal::entityTypeManager();
68 $this->installConfig(['filter', 'node', 'system']);
70 $this->installSchema('system', ['key_value_expire', 'sequences']);
71 $this->installSchema('node', ['node_access']);
73 $this->installEntitySchema('entity_test_mulrev');
74 $this->installEntitySchema('entity_test_mulrevpub');
75 $this->installEntitySchema('node');
76 $this->installEntitySchema('user');
78 $this->createContentType(['type' => 'page']);
80 $this->setCurrentUser($this->createUser(['administer nodes']));
82 // Create two nodes, a published and an unpublished one, so we can test the
83 // behavior of the module with default/existing content.
84 $this->createdTimestamp = \Drupal::time()->getRequestTime();
85 $this->createNode(['title' => 'live - 1 - r1 - published', 'created' => $this->createdTimestamp++, 'status' => TRUE]);
86 $this->createNode(['title' => 'live - 2 - r2 - unpublished', 'created' => $this->createdTimestamp++, 'status' => FALSE]);
90 * Tests various scenarios for creating and deploying content in workspaces.
92 public function testWorkspaces() {
93 $this->initializeWorkspacesModule();
95 // Notes about the structure of the test scenarios:
96 // - a multi-dimensional array keyed by the workspace ID, then by the entity
97 // ID and finally by the revision ID.
98 // - 'default_revision' indicates the entity revision that should be
99 // returned by entity_load(), non-revision entity queries and non-revision
100 // views *in a given workspace*, it does not indicate what is actually
101 // stored in the base and data entity tables.
102 $test_scenarios = [];
104 // The $expected_workspace_association array holds the revision IDs which
105 // should be tracked by the Workspace Association entity type in each test
106 // scenario, keyed by workspace ID.
107 $expected_workspace_association = [];
109 // In the initial state we have only the two revisions that were created
110 // before the Workspaces module was installed.
115 'title' => 'live - 1 - r1 - published',
117 'default_revision' => TRUE,
122 'title' => 'live - 2 - r2 - unpublished',
124 'default_revision' => TRUE,
131 'title' => 'live - 1 - r1 - published',
133 'default_revision' => TRUE,
138 'title' => 'live - 2 - r2 - unpublished',
140 'default_revision' => TRUE,
145 $test_scenarios['initial_state'] = $revision_state;
146 $expected_workspace_association['initial_state'] = ['stage' => []];
148 // Unpublish node 1 in 'stage'. The new revision is also added to 'live' but
149 // it is not the default revision.
150 $revision_state = array_replace_recursive($revision_state, [
154 'title' => 'stage - 1 - r3 - unpublished',
156 'default_revision' => FALSE,
162 1 => ['default_revision' => FALSE],
164 'title' => 'stage - 1 - r3 - unpublished',
166 'default_revision' => TRUE,
171 $test_scenarios['unpublish_node_1_in_stage'] = $revision_state;
172 $expected_workspace_association['unpublish_node_1_in_stage'] = ['stage' => [3]];
174 // Publish node 2 in 'stage'. The new revision is also added to 'live' but
175 // it is not the default revision.
176 $revision_state = array_replace_recursive($revision_state, [
180 'title' => 'stage - 2 - r4 - published',
182 'default_revision' => FALSE,
188 2 => ['default_revision' => FALSE],
190 'title' => 'stage - 2 - r4 - published',
192 'default_revision' => TRUE,
197 $test_scenarios['publish_node_2_in_stage'] = $revision_state;
198 $expected_workspace_association['publish_node_2_in_stage'] = ['stage' => [3, 4]];
200 // Adding a new unpublished node on 'stage' should create a single
201 // unpublished revision on both 'stage' and 'live'.
202 $revision_state = array_replace_recursive($revision_state, [
206 'title' => 'stage - 3 - r5 - unpublished',
208 'default_revision' => TRUE,
215 'title' => 'stage - 3 - r5 - unpublished',
217 'default_revision' => TRUE,
222 $test_scenarios['add_unpublished_node_in_stage'] = $revision_state;
223 $expected_workspace_association['add_unpublished_node_in_stage'] = ['stage' => [3, 4, 5]];
225 // Adding a new published node on 'stage' should create two revisions, an
226 // unpublished revision on 'live' and a published one on 'stage'.
227 $revision_state = array_replace_recursive($revision_state, [
231 'title' => 'stage - 4 - r6 - published',
233 'default_revision' => TRUE,
236 'title' => 'stage - 4 - r6 - published',
238 'default_revision' => FALSE,
245 'title' => 'stage - 4 - r6 - published',
247 'default_revision' => FALSE,
250 'title' => 'stage - 4 - r6 - published',
252 'default_revision' => TRUE,
257 $test_scenarios['add_published_node_in_stage'] = $revision_state;
258 $expected_workspace_association['add_published_node_in_stage'] = ['stage' => [3, 4, 5, 6, 7]];
260 // Deploying 'stage' to 'live' should simply make the latest revisions in
261 // 'stage' the default ones in 'live'.
262 $revision_state = array_replace_recursive($revision_state, [
265 1 => ['default_revision' => FALSE],
266 3 => ['default_revision' => TRUE],
269 2 => ['default_revision' => FALSE],
270 4 => ['default_revision' => TRUE],
272 // Node 3 has a single revision for both 'stage' and 'live' and it is
273 // already the default revision in both of them.
275 6 => ['default_revision' => FALSE],
276 7 => ['default_revision' => TRUE],
280 $test_scenarios['push_stage_to_live'] = $revision_state;
281 $expected_workspace_association['push_stage_to_live'] = ['stage' => []];
283 // Check the initial state after the module was installed.
284 $this->assertWorkspaceStatus($test_scenarios['initial_state'], 'node');
285 $this->assertWorkspaceAssociation($expected_workspace_association['initial_state'], 'node');
287 // Unpublish node 1 in 'stage'.
288 $this->switchToWorkspace('stage');
289 $node = $this->entityTypeManager->getStorage('node')->load(1);
290 $node->setTitle('stage - 1 - r3 - unpublished');
291 $node->setUnpublished();
293 $this->assertWorkspaceStatus($test_scenarios['unpublish_node_1_in_stage'], 'node');
294 $this->assertWorkspaceAssociation($expected_workspace_association['unpublish_node_1_in_stage'], 'node');
296 // Publish node 2 in 'stage'.
297 $this->switchToWorkspace('stage');
298 $node = $this->entityTypeManager->getStorage('node')->load(2);
299 $node->setTitle('stage - 2 - r4 - published');
300 $node->setPublished();
302 $this->assertWorkspaceStatus($test_scenarios['publish_node_2_in_stage'], 'node');
303 $this->assertWorkspaceAssociation($expected_workspace_association['publish_node_2_in_stage'], 'node');
305 // Add a new unpublished node on 'stage'.
306 $this->switchToWorkspace('stage');
307 $this->createNode(['title' => 'stage - 3 - r5 - unpublished', 'created' => $this->createdTimestamp++, 'status' => FALSE]);
308 $this->assertWorkspaceStatus($test_scenarios['add_unpublished_node_in_stage'], 'node');
309 $this->assertWorkspaceAssociation($expected_workspace_association['add_unpublished_node_in_stage'], 'node');
311 // Add a new published node on 'stage'.
312 $this->switchToWorkspace('stage');
313 $this->createNode(['title' => 'stage - 4 - r6 - published', 'created' => $this->createdTimestamp++, 'status' => TRUE]);
314 $this->assertWorkspaceStatus($test_scenarios['add_published_node_in_stage'], 'node');
315 $this->assertWorkspaceAssociation($expected_workspace_association['add_published_node_in_stage'], 'node');
317 // Deploy 'stage' to 'live'.
318 /** @var \Drupal\workspaces\WorkspacePublisher $workspace_publisher */
319 $workspace_publisher = \Drupal::service('workspaces.operation_factory')->getPublisher($this->workspaces['stage']);
321 // Check which revisions need to be pushed.
330 $this->assertEquals($expected, $workspace_publisher->getDifferringRevisionIdsOnSource());
332 $this->workspaces['stage']->publish();
333 $this->assertWorkspaceStatus($test_scenarios['push_stage_to_live'], 'node');
334 $this->assertWorkspaceAssociation($expected_workspace_association['push_stage_to_live'], 'node');
336 // Check that there are no more revisions to push.
337 $this->assertEmpty($workspace_publisher->getDifferringRevisionIdsOnSource());
341 * Tests the Entity Query relationship API with workspaces.
343 public function testEntityQueryRelationship() {
344 $this->initializeWorkspacesModule();
346 // Add an entity reference field that targets 'entity_test_mulrevpub'
348 $this->createEntityReferenceField('node', 'page', 'field_test_entity', 'Test entity reference', 'entity_test_mulrevpub');
350 // Add an entity reference field that targets 'node' entities so we can test
351 // references to the same base tables.
352 $this->createEntityReferenceField('node', 'page', 'field_test_node', 'Test node reference', 'node');
354 $this->switchToWorkspace('live');
355 $node_1 = $this->createNode([
356 'title' => 'live node 1',
358 $entity_test = EntityTestMulRevPub::create([
359 'name' => 'live entity_test_mulrevpub',
360 'non_rev_field' => 'live non-revisionable value',
362 $entity_test->save();
364 $node_2 = $this->createNode([
365 'title' => 'live node 2',
366 'field_test_entity' => $entity_test->id(),
367 'field_test_node' => $node_1->id(),
370 // Switch to the 'stage' workspace and change some values for the referenced
372 $this->switchToWorkspace('stage');
373 $node_1->title->value = 'stage node 1';
376 $node_2->title->value = 'stage node 2';
379 $entity_test->name->value = 'stage entity_test_mulrevpub';
380 $entity_test->non_rev_field->value = 'stage non-revisionable value';
381 $entity_test->save();
383 // Make sure that we're requesting the default revision.
384 $query = $this->entityTypeManager->getStorage('node')->getQuery();
385 $query->currentRevision();
388 // Check a condition on the revision data table.
389 ->condition('title', 'stage node 2')
390 // Check a condition on the revision table.
391 ->condition('revision_uid', $node_2->getRevisionUserId())
392 // Check a condition on the data table.
393 ->condition('type', $node_2->bundle())
394 // Check a condition on the base table.
395 ->condition('uuid', $node_2->uuid());
397 // Add conditions for a reference to the same entity type.
399 // Check a condition on the revision data table.
400 ->condition('field_test_node.entity.title', 'stage node 1')
401 // Check a condition on the revision table.
402 ->condition('field_test_node.entity.revision_uid', $node_1->getRevisionUserId())
403 // Check a condition on the data table.
404 ->condition('field_test_node.entity.type', $node_1->bundle())
405 // Check a condition on the base table.
406 ->condition('field_test_node.entity.uuid', $node_1->uuid());
408 // Add conditions for a reference to a different entity type.
409 // @todo Re-enable the two conditions below when we find a way to not join
410 // the workspace_association table for every duplicate entity base table
412 // @see https://www.drupal.org/project/drupal/issues/2983639
414 // Check a condition on the revision data table.
415 // ->condition('field_test_entity.entity.name', 'stage entity_test_mulrevpub')
416 // Check a condition on the data table.
417 // ->condition('field_test_entity.entity.non_rev_field', 'stage non-revisionable value')
418 // Check a condition on the base table.
419 ->condition('field_test_entity.entity.uuid', $entity_test->uuid());
421 $result = $query->execute();
422 $this->assertSame([$node_2->getRevisionId() => $node_2->id()], $result);
426 * Tests CRUD operations for unsupported entity types.
428 public function testDisallowedEntityCRUDInNonDefaultWorkspace() {
429 $this->initializeWorkspacesModule();
431 // Create an unsupported entity type in the default workspace.
432 $this->switchToWorkspace('live');
433 $entity_test = EntityTestMulRev::create([
434 'name' => 'live entity_test_mulrev',
436 $entity_test->save();
438 // Switch to a non-default workspace and check that any entity type CRUD are
440 $this->switchToWorkspace('stage');
442 // Check updating an existing entity.
443 $entity_test->name->value = 'stage entity_test_mulrev';
444 $entity_test->setNewRevision(TRUE);
445 $this->setExpectedException(EntityStorageException::class, 'This entity can only be saved in the default workspace.');
446 $entity_test->save();
448 // Check saving a new entity.
449 $new_entity_test = EntityTestMulRev::create([
450 'name' => 'stage entity_test_mulrev',
452 $this->setExpectedException(EntityStorageException::class, 'This entity can only be saved in the default workspace.');
453 $new_entity_test->save();
455 // Check deleting an existing entity.
456 $this->setExpectedException(EntityStorageException::class, 'This entity can only be deleted in the default workspace.');
457 $entity_test->delete();
461 * @covers \Drupal\workspaces\WorkspaceManager::executeInWorkspace
463 public function testExecuteInWorkspaceContext() {
464 $this->initializeWorkspacesModule();
466 // Create an entity in the default workspace.
467 $this->switchToWorkspace('live');
468 $node = $this->createNode([
469 'title' => 'live node 1',
473 // Switch to the 'stage' workspace and change some values for the referenced
475 $this->switchToWorkspace('stage');
476 $node->title->value = 'stage node 1';
479 // Switch back to the default workspace and run the baseline assertions.
480 $this->switchToWorkspace('live');
481 $storage = $this->entityTypeManager->getStorage('node');
483 $this->assertEquals('live', $this->workspaceManager->getActiveWorkspace()->id());
485 $live_node = $storage->loadUnchanged($node->id());
486 $this->assertEquals('live node 1', $live_node->title->value);
488 $result = $storage->getQuery()
489 ->condition('title', 'live node 1')
491 $this->assertEquals([$live_node->getRevisionId() => $node->id()], $result);
493 // Try the same assertions in the context of the 'stage' workspace.
494 $this->workspaceManager->executeInWorkspace('stage', function () use ($node, $storage) {
495 $this->assertEquals('stage', $this->workspaceManager->getActiveWorkspace()->id());
497 $stage_node = $storage->loadUnchanged($node->id());
498 $this->assertEquals('stage node 1', $stage_node->title->value);
500 $result = $storage->getQuery()
501 ->condition('title', 'stage node 1')
503 $this->assertEquals([$stage_node->getRevisionId() => $stage_node->id()], $result);
506 // Check that the 'stage' workspace was not persisted by the workspace
508 $this->assertEquals('live', $this->workspaceManager->getActiveWorkspace()->id());
512 * Checks entity load, entity queries and views results for a test scenario.
514 * @param array $expected
515 * An array of expected values, as defined in ::testWorkspaces().
516 * @param string $entity_type_id
517 * The ID of the entity type that is being tested.
519 protected function assertWorkspaceStatus(array $expected, $entity_type_id) {
520 $expected = $this->flattenExpectedValues($expected, $entity_type_id);
522 $entity_keys = $this->entityTypeManager->getDefinition($entity_type_id)->getKeys();
523 foreach ($expected as $workspace_id => $expected_values) {
524 $this->switchToWorkspace($workspace_id);
526 // Check that default revisions are swapped with the workspace revision.
527 $this->assertEntityLoad($expected_values, $entity_type_id);
529 // Check that non-default revisions are not changed.
530 $this->assertEntityRevisionLoad($expected_values, $entity_type_id);
532 // Check that entity queries return the correct results.
533 $this->assertEntityQuery($expected_values, $entity_type_id);
535 // Check that the 'Frontpage' view only shows published content that is
536 // also considered as the default revision in the given workspace.
537 $expected_frontpage = array_filter($expected_values, function ($expected_value) {
538 return $expected_value['status'] === TRUE && $expected_value['default_revision'] === TRUE;
540 // The 'Frontpage' view will output nodes in reverse creation order.
541 usort($expected_frontpage, function ($a, $b) {
542 return $b['nid'] - $a['nid'];
544 $view = Views::getView('frontpage');
546 $this->assertIdenticalResultset($view, $expected_frontpage, ['nid' => 'nid']);
548 $rendered_view = $view->render('page_1');
549 $output = \Drupal::service('renderer')->renderRoot($rendered_view);
550 $this->setRawContent($output);
551 foreach ($expected_values as $expected_entity_values) {
552 if ($expected_entity_values[$entity_keys['published']] === TRUE && $expected_entity_values['default_revision'] === TRUE) {
553 $this->assertRaw($expected_entity_values[$entity_keys['label']]);
555 // Node 4 will always appear in the 'stage' workspace because it has
556 // both an unpublished revision as well as a published one.
557 elseif ($workspace_id != 'stage' && $expected_entity_values[$entity_keys['id']] != 4) {
558 $this->assertNoRaw($expected_entity_values[$entity_keys['label']]);
565 * Asserts that default revisions are properly swapped in a workspace.
567 * @param array $expected_values
568 * An array of expected values, as defined in ::testWorkspaces().
569 * @param string $entity_type_id
570 * The ID of the entity type to check.
572 protected function assertEntityLoad(array $expected_values, $entity_type_id) {
573 // Filter the expected values so we can check only the default revisions.
574 $expected_default_revisions = array_filter($expected_values, function ($expected_value) {
575 return $expected_value['default_revision'] === TRUE;
578 $entity_keys = $this->entityTypeManager->getDefinition($entity_type_id)->getKeys();
579 $id_key = $entity_keys['id'];
580 $revision_key = $entity_keys['revision'];
581 $label_key = $entity_keys['label'];
582 $published_key = $entity_keys['published'];
584 // Check \Drupal\Core\Entity\EntityStorageInterface::loadMultiple().
585 /** @var \Drupal\Core\Entity\EntityInterface[]|\Drupal\Core\Entity\RevisionableInterface[]|\Drupal\Core\Entity\EntityPublishedInterface[] $entities */
586 $entities = $this->entityTypeManager->getStorage($entity_type_id)->loadMultiple(array_column($expected_default_revisions, $id_key));
587 foreach ($expected_default_revisions as $expected_default_revision) {
588 $entity_id = $expected_default_revision[$id_key];
589 $this->assertEquals($expected_default_revision[$revision_key], $entities[$entity_id]->getRevisionId());
590 $this->assertEquals($expected_default_revision[$label_key], $entities[$entity_id]->label());
591 $this->assertEquals($expected_default_revision[$published_key], $entities[$entity_id]->isPublished());
594 // Check \Drupal\Core\Entity\EntityStorageInterface::loadUnchanged().
595 foreach ($expected_default_revisions as $expected_default_revision) {
596 /** @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface|\Drupal\Core\Entity\EntityPublishedInterface $entity */
597 $entity = $this->entityTypeManager->getStorage($entity_type_id)->loadUnchanged($expected_default_revision[$id_key]);
598 $this->assertEquals($expected_default_revision[$revision_key], $entity->getRevisionId());
599 $this->assertEquals($expected_default_revision[$label_key], $entity->label());
600 $this->assertEquals($expected_default_revision[$published_key], $entity->isPublished());
605 * Asserts that non-default revisions are not changed.
607 * @param array $expected_values
608 * An array of expected values, as defined in ::testWorkspaces().
609 * @param string $entity_type_id
610 * The ID of the entity type to check.
612 protected function assertEntityRevisionLoad(array $expected_values, $entity_type_id) {
613 $entity_keys = $this->entityTypeManager->getDefinition($entity_type_id)->getKeys();
614 $id_key = $entity_keys['id'];
615 $revision_key = $entity_keys['revision'];
616 $label_key = $entity_keys['label'];
617 $published_key = $entity_keys['published'];
619 /** @var \Drupal\Core\Entity\EntityInterface[]|\Drupal\Core\Entity\RevisionableInterface[]|\Drupal\Core\Entity\EntityPublishedInterface[] $entities */
620 $entities = $this->entityTypeManager->getStorage($entity_type_id)->loadMultipleRevisions(array_column($expected_values, $revision_key));
621 foreach ($expected_values as $expected_revision) {
622 $revision_id = $expected_revision[$revision_key];
623 $this->assertEquals($expected_revision[$id_key], $entities[$revision_id]->id());
624 $this->assertEquals($expected_revision[$revision_key], $entities[$revision_id]->getRevisionId());
625 $this->assertEquals($expected_revision[$label_key], $entities[$revision_id]->label());
626 $this->assertEquals($expected_revision[$published_key], $entities[$revision_id]->isPublished());
631 * Asserts that entity queries are giving the correct results in a workspace.
633 * @param array $expected_values
634 * An array of expected values, as defined in ::testWorkspaces().
635 * @param string $entity_type_id
636 * The ID of the entity type to check.
638 protected function assertEntityQuery(array $expected_values, $entity_type_id) {
639 $storage = $this->entityTypeManager->getStorage($entity_type_id);
640 $entity_keys = $this->entityTypeManager->getDefinition($entity_type_id)->getKeys();
641 $id_key = $entity_keys['id'];
642 $revision_key = $entity_keys['revision'];
643 $label_key = $entity_keys['label'];
644 $published_key = $entity_keys['published'];
646 // Filter the expected values so we can check only the default revisions.
647 $expected_default_revisions = array_filter($expected_values, function ($expected_value) {
648 return $expected_value['default_revision'] === TRUE;
651 // Check entity query counts.
652 $result = $storage->getQuery()->count()->execute();
653 $this->assertEquals(count($expected_default_revisions), $result);
655 $result = $storage->getAggregateQuery()->count()->execute();
656 $this->assertEquals(count($expected_default_revisions), $result);
658 // Check entity queries with no conditions.
659 $result = $storage->getQuery()->execute();
660 $expected_result = array_combine(array_column($expected_default_revisions, $revision_key), array_column($expected_default_revisions, $id_key));
661 $this->assertEquals($expected_result, $result);
663 // Check querying each revision individually.
664 foreach ($expected_values as $expected_value) {
665 $query = $storage->getQuery();
667 ->condition($entity_keys['id'], $expected_value[$id_key])
668 ->condition($entity_keys['label'], $expected_value[$label_key])
669 ->condition($entity_keys['published'], (int) $expected_value[$published_key]);
671 // If the entity is not expected to be the default revision, we need to
672 // query all revisions if we want to find it.
673 if (!$expected_value['default_revision']) {
674 $query->allRevisions();
677 $result = $query->execute();
678 $this->assertEquals([$expected_value[$revision_key] => $expected_value[$id_key]], $result);
683 * Checks the workspace_association entries for a test scenario.
685 * @param array $expected
686 * An array of expected values, as defined in ::testWorkspaces().
687 * @param string $entity_type_id
688 * The ID of the entity type that is being tested.
690 protected function assertWorkspaceAssociation(array $expected, $entity_type_id) {
691 /** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */
692 $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association');
693 foreach ($expected as $workspace_id => $expected_tracked_revision_ids) {
694 $tracked_entities = $workspace_association_storage->getTrackedEntities($workspace_id, TRUE);
695 $tracked_revision_ids = isset($tracked_entities[$entity_type_id]) ? $tracked_entities[$entity_type_id] : [];
696 $this->assertEquals($expected_tracked_revision_ids, array_keys($tracked_revision_ids));
701 * Flattens the expectations array defined by testWorkspaces().
703 * @param array $expected
704 * An array as defined by testWorkspaces().
705 * @param string $entity_type_id
706 * The ID of the entity type that is being tested.
709 * An array where all the entity IDs and revision IDs are merged inside each
710 * expected values array.
712 protected function flattenExpectedValues(array $expected, $entity_type_id) {
715 $entity_keys = $this->entityTypeManager->getDefinition($entity_type_id)->getKeys();
716 foreach ($expected as $workspace_id => $workspace_values) {
717 foreach ($workspace_values as $entity_id => $entity_revisions) {
718 foreach ($entity_revisions as $revision_id => $revision_values) {
719 $flattened[$workspace_id][] = [$entity_keys['id'] => $entity_id, $entity_keys['revision'] => $revision_id] + $revision_values;
728 * Tests that entity forms can be stored in the form cache.
730 public function testFormCacheForEntityForms() {
731 $this->initializeWorkspacesModule();
732 $this->switchToWorkspace('stage');
734 $form_builder = $this->container->get('form_builder');
736 $form = $this->entityTypeManager->getFormObject('entity_test_mulrevpub', 'default');
737 $form->setEntity(EntityTestMulRevPub::create([]));
739 $form_state = new FormState();
740 $built_form = $form_builder->buildForm($form, $form_state);
741 $form_builder->setCache($built_form['#build_id'], $built_form, $form_state);
745 * Tests that non-entity forms can be stored in the form cache.
747 public function testFormCacheForRegularForms() {
748 $this->initializeWorkspacesModule();
749 $this->switchToWorkspace('stage');
751 $form_builder = $this->container->get('form_builder');
753 $form_state = new FormState();
754 $built_form = $form_builder->getForm(SiteInformationForm::class, $form_state);
755 $form_builder->setCache($built_form['#build_id'], $built_form, $form_state);