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;
17 use Drupal\workspaces\Entity\Workspace;
20 * Tests a complete deployment scenario across different workspaces.
24 class WorkspaceIntegrationTest extends KernelTestBase {
26 use ContentTypeCreationTrait;
27 use EntityReferenceTestTrait;
28 use NodeCreationTrait;
29 use UserCreationTrait;
30 use ViewResultAssertionTrait;
33 * The entity type manager.
35 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
37 protected $entityTypeManager;
40 * An array of test workspaces, keyed by workspace ID.
42 * @var \Drupal\workspaces\WorkspaceInterface[]
44 protected $workspaces = [];
47 * Creation timestamp that should be incremented for each new entity.
51 protected $createdTimestamp;
56 protected static $modules = [
70 protected function setUp() {
73 $this->entityTypeManager = \Drupal::entityTypeManager();
75 $this->installConfig(['filter', 'node', 'system']);
77 $this->installSchema('system', ['key_value_expire', 'sequences']);
78 $this->installSchema('node', ['node_access']);
80 $this->installEntitySchema('entity_test_mulrev');
81 $this->installEntitySchema('entity_test_mulrevpub');
82 $this->installEntitySchema('node');
83 $this->installEntitySchema('user');
85 $this->createContentType(['type' => 'page']);
87 $this->setCurrentUser($this->createUser(['administer nodes']));
89 // Create two nodes, a published and an unpublished one, so we can test the
90 // behavior of the module with default/existing content.
91 $this->createdTimestamp = \Drupal::time()->getRequestTime();
92 $this->createNode(['title' => 'live - 1 - r1 - published', 'created' => $this->createdTimestamp++, 'status' => TRUE]);
93 $this->createNode(['title' => 'live - 2 - r2 - unpublished', 'created' => $this->createdTimestamp++, 'status' => FALSE]);
97 * Enables the Workspaces module and creates two workspaces.
99 protected function initializeWorkspacesModule() {
100 // Enable the Workspaces module here instead of the static::$modules array
101 // so we can test it with default content.
102 $this->enableModules(['workspaces']);
103 $this->container = \Drupal::getContainer();
104 $this->entityTypeManager = \Drupal::entityTypeManager();
106 $this->installEntitySchema('workspace');
107 $this->installEntitySchema('workspace_association');
109 // Create two workspaces by default, 'live' and 'stage'.
110 $this->workspaces['live'] = Workspace::create(['id' => 'live']);
111 $this->workspaces['live']->save();
112 $this->workspaces['stage'] = Workspace::create(['id' => 'stage']);
113 $this->workspaces['stage']->save();
118 'edit any workspace',
119 'view any workspace',
121 $this->setCurrentUser($this->createUser($permissions));
125 * Tests various scenarios for creating and deploying content in workspaces.
127 public function testWorkspaces() {
128 $this->initializeWorkspacesModule();
130 // Notes about the structure of the test scenarios:
131 // - a multi-dimensional array keyed by the workspace ID, then by the entity
132 // ID and finally by the revision ID.
133 // - 'default_revision' indicates the entity revision that should be
134 // returned by entity_load(), non-revision entity queries and non-revision
135 // views *in a given workspace*, it does not indicate what is actually
136 // stored in the base and data entity tables.
137 $test_scenarios = [];
139 // The $expected_workspace_association array holds the revision IDs which
140 // should be tracked by the Workspace Association entity type in each test
141 // scenario, keyed by workspace ID.
142 $expected_workspace_association = [];
144 // In the initial state we have only the two revisions that were created
145 // before the Workspaces module was installed.
150 'title' => 'live - 1 - r1 - published',
152 'default_revision' => TRUE,
157 'title' => 'live - 2 - r2 - unpublished',
159 'default_revision' => TRUE,
166 'title' => 'live - 1 - r1 - published',
168 'default_revision' => TRUE,
173 'title' => 'live - 2 - r2 - unpublished',
175 'default_revision' => TRUE,
180 $test_scenarios['initial_state'] = $revision_state;
181 $expected_workspace_association['initial_state'] = ['stage' => []];
183 // Unpublish node 1 in 'stage'. The new revision is also added to 'live' but
184 // it is not the default revision.
185 $revision_state = array_replace_recursive($revision_state, [
189 'title' => 'stage - 1 - r3 - unpublished',
191 'default_revision' => FALSE,
197 1 => ['default_revision' => FALSE],
199 'title' => 'stage - 1 - r3 - unpublished',
201 'default_revision' => TRUE,
206 $test_scenarios['unpublish_node_1_in_stage'] = $revision_state;
207 $expected_workspace_association['unpublish_node_1_in_stage'] = ['stage' => [3]];
209 // Publish node 2 in 'stage'. The new revision is also added to 'live' but
210 // it is not the default revision.
211 $revision_state = array_replace_recursive($revision_state, [
215 'title' => 'stage - 2 - r4 - published',
217 'default_revision' => FALSE,
223 2 => ['default_revision' => FALSE],
225 'title' => 'stage - 2 - r4 - published',
227 'default_revision' => TRUE,
232 $test_scenarios['publish_node_2_in_stage'] = $revision_state;
233 $expected_workspace_association['publish_node_2_in_stage'] = ['stage' => [3, 4]];
235 // Adding a new unpublished node on 'stage' should create a single
236 // unpublished revision on both 'stage' and 'live'.
237 $revision_state = array_replace_recursive($revision_state, [
241 'title' => 'stage - 3 - r5 - unpublished',
243 'default_revision' => TRUE,
250 'title' => 'stage - 3 - r5 - unpublished',
252 'default_revision' => TRUE,
257 $test_scenarios['add_unpublished_node_in_stage'] = $revision_state;
258 $expected_workspace_association['add_unpublished_node_in_stage'] = ['stage' => [3, 4, 5]];
260 // Adding a new published node on 'stage' should create two revisions, an
261 // unpublished revision on 'live' and a published one on 'stage'.
262 $revision_state = array_replace_recursive($revision_state, [
266 'title' => 'stage - 4 - r6 - published',
268 'default_revision' => TRUE,
271 'title' => 'stage - 4 - r6 - published',
273 'default_revision' => FALSE,
280 'title' => 'stage - 4 - r6 - published',
282 'default_revision' => FALSE,
285 'title' => 'stage - 4 - r6 - published',
287 'default_revision' => TRUE,
292 $test_scenarios['add_published_node_in_stage'] = $revision_state;
293 $expected_workspace_association['add_published_node_in_stage'] = ['stage' => [3, 4, 5, 6, 7]];
295 // Deploying 'stage' to 'live' should simply make the latest revisions in
296 // 'stage' the default ones in 'live'.
297 $revision_state = array_replace_recursive($revision_state, [
300 1 => ['default_revision' => FALSE],
301 3 => ['default_revision' => TRUE],
304 2 => ['default_revision' => FALSE],
305 4 => ['default_revision' => TRUE],
307 // Node 3 has a single revision for both 'stage' and 'live' and it is
308 // already the default revision in both of them.
310 6 => ['default_revision' => FALSE],
311 7 => ['default_revision' => TRUE],
315 $test_scenarios['push_stage_to_live'] = $revision_state;
316 $expected_workspace_association['push_stage_to_live'] = ['stage' => []];
318 // Check the initial state after the module was installed.
319 $this->assertWorkspaceStatus($test_scenarios['initial_state'], 'node');
320 $this->assertWorkspaceAssociation($expected_workspace_association['initial_state'], 'node');
322 // Unpublish node 1 in 'stage'.
323 $this->switchToWorkspace('stage');
324 $node = $this->entityTypeManager->getStorage('node')->load(1);
325 $node->setTitle('stage - 1 - r3 - unpublished');
326 $node->setUnpublished();
328 $this->assertWorkspaceStatus($test_scenarios['unpublish_node_1_in_stage'], 'node');
329 $this->assertWorkspaceAssociation($expected_workspace_association['unpublish_node_1_in_stage'], 'node');
331 // Publish node 2 in 'stage'.
332 $this->switchToWorkspace('stage');
333 $node = $this->entityTypeManager->getStorage('node')->load(2);
334 $node->setTitle('stage - 2 - r4 - published');
335 $node->setPublished();
337 $this->assertWorkspaceStatus($test_scenarios['publish_node_2_in_stage'], 'node');
338 $this->assertWorkspaceAssociation($expected_workspace_association['publish_node_2_in_stage'], 'node');
340 // Add a new unpublished node on 'stage'.
341 $this->switchToWorkspace('stage');
342 $this->createNode(['title' => 'stage - 3 - r5 - unpublished', 'created' => $this->createdTimestamp++, 'status' => FALSE]);
343 $this->assertWorkspaceStatus($test_scenarios['add_unpublished_node_in_stage'], 'node');
344 $this->assertWorkspaceAssociation($expected_workspace_association['add_unpublished_node_in_stage'], 'node');
346 // Add a new published node on 'stage'.
347 $this->switchToWorkspace('stage');
348 $this->createNode(['title' => 'stage - 4 - r6 - published', 'created' => $this->createdTimestamp++, 'status' => TRUE]);
349 $this->assertWorkspaceStatus($test_scenarios['add_published_node_in_stage'], 'node');
350 $this->assertWorkspaceAssociation($expected_workspace_association['add_published_node_in_stage'], 'node');
352 // Deploy 'stage' to 'live'.
353 /** @var \Drupal\workspaces\WorkspacePublisher $workspace_publisher */
354 $workspace_publisher = \Drupal::service('workspaces.operation_factory')->getPublisher($this->workspaces['stage']);
356 // Check which revisions need to be pushed.
365 $this->assertEquals($expected, $workspace_publisher->getDifferringRevisionIdsOnSource());
367 $this->workspaces['stage']->publish();
368 $this->assertWorkspaceStatus($test_scenarios['push_stage_to_live'], 'node');
369 $this->assertWorkspaceAssociation($expected_workspace_association['push_stage_to_live'], 'node');
371 // Check that there are no more revisions to push.
372 $this->assertEmpty($workspace_publisher->getDifferringRevisionIdsOnSource());
376 * Tests the Entity Query relationship API with workspaces.
378 public function testEntityQueryRelationship() {
379 $this->initializeWorkspacesModule();
381 // Add an entity reference field that targets 'entity_test_mulrevpub'
383 $this->createEntityReferenceField('node', 'page', 'field_test_entity', 'Test entity reference', 'entity_test_mulrevpub');
385 // Add an entity reference field that targets 'node' entities so we can test
386 // references to the same base tables.
387 $this->createEntityReferenceField('node', 'page', 'field_test_node', 'Test node reference', 'node');
389 $this->switchToWorkspace('live');
390 $node_1 = $this->createNode([
391 'title' => 'live node 1',
393 $entity_test = EntityTestMulRevPub::create([
394 'name' => 'live entity_test_mulrevpub',
395 'non_rev_field' => 'live non-revisionable value',
397 $entity_test->save();
399 $node_2 = $this->createNode([
400 'title' => 'live node 2',
401 'field_test_entity' => $entity_test->id(),
402 'field_test_node' => $node_1->id(),
405 // Switch to the 'stage' workspace and change some values for the referenced
407 $this->switchToWorkspace('stage');
408 $node_1->title->value = 'stage node 1';
411 $node_2->title->value = 'stage node 2';
414 $entity_test->name->value = 'stage entity_test_mulrevpub';
415 $entity_test->non_rev_field->value = 'stage non-revisionable value';
416 $entity_test->save();
418 // Make sure that we're requesting the default revision.
419 $query = $this->entityTypeManager->getStorage('node')->getQuery();
420 $query->currentRevision();
423 // Check a condition on the revision data table.
424 ->condition('title', 'stage node 2')
425 // Check a condition on the revision table.
426 ->condition('revision_uid', $node_2->getRevisionUserId())
427 // Check a condition on the data table.
428 ->condition('type', $node_2->bundle())
429 // Check a condition on the base table.
430 ->condition('uuid', $node_2->uuid());
432 // Add conditions for a reference to the same entity type.
434 // Check a condition on the revision data table.
435 ->condition('field_test_node.entity.title', 'stage node 1')
436 // Check a condition on the revision table.
437 ->condition('field_test_node.entity.revision_uid', $node_1->getRevisionUserId())
438 // Check a condition on the data table.
439 ->condition('field_test_node.entity.type', $node_1->bundle())
440 // Check a condition on the base table.
441 ->condition('field_test_node.entity.uuid', $node_1->uuid());
443 // Add conditions for a reference to a different entity type.
444 // @todo Re-enable the two conditions below when we find a way to not join
445 // the workspace_association table for every duplicate entity base table
447 // @see https://www.drupal.org/project/drupal/issues/2983639
449 // Check a condition on the revision data table.
450 // ->condition('field_test_entity.entity.name', 'stage entity_test_mulrevpub')
451 // Check a condition on the data table.
452 // ->condition('field_test_entity.entity.non_rev_field', 'stage non-revisionable value')
453 // Check a condition on the base table.
454 ->condition('field_test_entity.entity.uuid', $entity_test->uuid());
456 $result = $query->execute();
457 $this->assertSame([$node_2->getRevisionId() => $node_2->id()], $result);
461 * Tests CRUD operations for unsupported entity types.
463 public function testDisallowedEntityCRUDInNonDefaultWorkspace() {
464 $this->initializeWorkspacesModule();
466 // Create an unsupported entity type in the default workspace.
467 $this->switchToWorkspace('live');
468 $entity_test = EntityTestMulRev::create([
469 'name' => 'live entity_test_mulrev',
471 $entity_test->save();
473 // Switch to a non-default workspace and check that any entity type CRUD are
475 $this->switchToWorkspace('stage');
477 // Check updating an existing entity.
478 $entity_test->name->value = 'stage entity_test_mulrev';
479 $entity_test->setNewRevision(TRUE);
480 $this->setExpectedException(EntityStorageException::class, 'This entity can only be saved in the default workspace.');
481 $entity_test->save();
483 // Check saving a new entity.
484 $new_entity_test = EntityTestMulRev::create([
485 'name' => 'stage entity_test_mulrev',
487 $this->setExpectedException(EntityStorageException::class, 'This entity can only be saved in the default workspace.');
488 $new_entity_test->save();
490 // Check deleting an existing entity.
491 $this->setExpectedException(EntityStorageException::class, 'This entity can only be deleted in the default workspace.');
492 $entity_test->delete();
496 * Checks entity load, entity queries and views results for a test scenario.
498 * @param array $expected
499 * An array of expected values, as defined in ::testWorkspaces().
500 * @param string $entity_type_id
501 * The ID of the entity type that is being tested.
503 protected function assertWorkspaceStatus(array $expected, $entity_type_id) {
504 $expected = $this->flattenExpectedValues($expected, $entity_type_id);
506 $entity_keys = $this->entityTypeManager->getDefinition($entity_type_id)->getKeys();
507 foreach ($expected as $workspace_id => $expected_values) {
508 $this->switchToWorkspace($workspace_id);
510 // Check that default revisions are swapped with the workspace revision.
511 $this->assertEntityLoad($expected_values, $entity_type_id);
513 // Check that non-default revisions are not changed.
514 $this->assertEntityRevisionLoad($expected_values, $entity_type_id);
516 // Check that entity queries return the correct results.
517 $this->assertEntityQuery($expected_values, $entity_type_id);
519 // Check that the 'Frontpage' view only shows published content that is
520 // also considered as the default revision in the given workspace.
521 $expected_frontpage = array_filter($expected_values, function ($expected_value) {
522 return $expected_value['status'] === TRUE && $expected_value['default_revision'] === TRUE;
524 // The 'Frontpage' view will output nodes in reverse creation order.
525 usort($expected_frontpage, function ($a, $b) {
526 return $b['nid'] - $a['nid'];
528 $view = Views::getView('frontpage');
530 $this->assertIdenticalResultset($view, $expected_frontpage, ['nid' => 'nid']);
532 $rendered_view = $view->render('page_1');
533 $output = \Drupal::service('renderer')->renderRoot($rendered_view);
534 $this->setRawContent($output);
535 foreach ($expected_values as $expected_entity_values) {
536 if ($expected_entity_values[$entity_keys['published']] === TRUE && $expected_entity_values['default_revision'] === TRUE) {
537 $this->assertRaw($expected_entity_values[$entity_keys['label']]);
539 // Node 4 will always appear in the 'stage' workspace because it has
540 // both an unpublished revision as well as a published one.
541 elseif ($workspace_id != 'stage' && $expected_entity_values[$entity_keys['id']] != 4) {
542 $this->assertNoRaw($expected_entity_values[$entity_keys['label']]);
549 * Asserts that default revisions are properly swapped in a workspace.
551 * @param array $expected_values
552 * An array of expected values, as defined in ::testWorkspaces().
553 * @param string $entity_type_id
554 * The ID of the entity type to check.
556 protected function assertEntityLoad(array $expected_values, $entity_type_id) {
557 // Filter the expected values so we can check only the default revisions.
558 $expected_default_revisions = array_filter($expected_values, function ($expected_value) {
559 return $expected_value['default_revision'] === TRUE;
562 $entity_keys = $this->entityTypeManager->getDefinition($entity_type_id)->getKeys();
563 $id_key = $entity_keys['id'];
564 $revision_key = $entity_keys['revision'];
565 $label_key = $entity_keys['label'];
566 $published_key = $entity_keys['published'];
568 // Check \Drupal\Core\Entity\EntityStorageInterface::loadMultiple().
569 /** @var \Drupal\Core\Entity\EntityInterface[]|\Drupal\Core\Entity\RevisionableInterface[]|\Drupal\Core\Entity\EntityPublishedInterface[] $entities */
570 $entities = $this->entityTypeManager->getStorage($entity_type_id)->loadMultiple(array_column($expected_default_revisions, $id_key));
571 foreach ($expected_default_revisions as $expected_default_revision) {
572 $entity_id = $expected_default_revision[$id_key];
573 $this->assertEquals($expected_default_revision[$revision_key], $entities[$entity_id]->getRevisionId());
574 $this->assertEquals($expected_default_revision[$label_key], $entities[$entity_id]->label());
575 $this->assertEquals($expected_default_revision[$published_key], $entities[$entity_id]->isPublished());
578 // Check \Drupal\Core\Entity\EntityStorageInterface::loadUnchanged().
579 foreach ($expected_default_revisions as $expected_default_revision) {
580 /** @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface|\Drupal\Core\Entity\EntityPublishedInterface $entity */
581 $entity = $this->entityTypeManager->getStorage($entity_type_id)->loadUnchanged($expected_default_revision[$id_key]);
582 $this->assertEquals($expected_default_revision[$revision_key], $entity->getRevisionId());
583 $this->assertEquals($expected_default_revision[$label_key], $entity->label());
584 $this->assertEquals($expected_default_revision[$published_key], $entity->isPublished());
589 * Asserts that non-default revisions are not changed.
591 * @param array $expected_values
592 * An array of expected values, as defined in ::testWorkspaces().
593 * @param string $entity_type_id
594 * The ID of the entity type to check.
596 protected function assertEntityRevisionLoad(array $expected_values, $entity_type_id) {
597 $entity_keys = $this->entityTypeManager->getDefinition($entity_type_id)->getKeys();
598 $id_key = $entity_keys['id'];
599 $revision_key = $entity_keys['revision'];
600 $label_key = $entity_keys['label'];
601 $published_key = $entity_keys['published'];
603 /** @var \Drupal\Core\Entity\EntityInterface[]|\Drupal\Core\Entity\RevisionableInterface[]|\Drupal\Core\Entity\EntityPublishedInterface[] $entities */
604 $entities = $this->entityTypeManager->getStorage($entity_type_id)->loadMultipleRevisions(array_column($expected_values, $revision_key));
605 foreach ($expected_values as $expected_revision) {
606 $revision_id = $expected_revision[$revision_key];
607 $this->assertEquals($expected_revision[$id_key], $entities[$revision_id]->id());
608 $this->assertEquals($expected_revision[$revision_key], $entities[$revision_id]->getRevisionId());
609 $this->assertEquals($expected_revision[$label_key], $entities[$revision_id]->label());
610 $this->assertEquals($expected_revision[$published_key], $entities[$revision_id]->isPublished());
615 * Asserts that entity queries are giving the correct results in a workspace.
617 * @param array $expected_values
618 * An array of expected values, as defined in ::testWorkspaces().
619 * @param string $entity_type_id
620 * The ID of the entity type to check.
622 protected function assertEntityQuery(array $expected_values, $entity_type_id) {
623 $storage = $this->entityTypeManager->getStorage($entity_type_id);
624 $entity_keys = $this->entityTypeManager->getDefinition($entity_type_id)->getKeys();
625 $id_key = $entity_keys['id'];
626 $revision_key = $entity_keys['revision'];
627 $label_key = $entity_keys['label'];
628 $published_key = $entity_keys['published'];
630 // Filter the expected values so we can check only the default revisions.
631 $expected_default_revisions = array_filter($expected_values, function ($expected_value) {
632 return $expected_value['default_revision'] === TRUE;
635 // Check entity query counts.
636 $result = $storage->getQuery()->count()->execute();
637 $this->assertEquals(count($expected_default_revisions), $result);
639 $result = $storage->getAggregateQuery()->count()->execute();
640 $this->assertEquals(count($expected_default_revisions), $result);
642 // Check entity queries with no conditions.
643 $result = $storage->getQuery()->execute();
644 $expected_result = array_combine(array_column($expected_default_revisions, $revision_key), array_column($expected_default_revisions, $id_key));
645 $this->assertEquals($expected_result, $result);
647 // Check querying each revision individually.
648 foreach ($expected_values as $expected_value) {
649 $query = $storage->getQuery();
651 ->condition($entity_keys['id'], $expected_value[$id_key])
652 ->condition($entity_keys['label'], $expected_value[$label_key])
653 ->condition($entity_keys['published'], (int) $expected_value[$published_key]);
655 // If the entity is not expected to be the default revision, we need to
656 // query all revisions if we want to find it.
657 if (!$expected_value['default_revision']) {
658 $query->allRevisions();
661 $result = $query->execute();
662 $this->assertEquals([$expected_value[$revision_key] => $expected_value[$id_key]], $result);
667 * Checks the workspace_association entries for a test scenario.
669 * @param array $expected
670 * An array of expected values, as defined in ::testWorkspaces().
671 * @param string $entity_type_id
672 * The ID of the entity type that is being tested.
674 protected function assertWorkspaceAssociation(array $expected, $entity_type_id) {
675 /** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */
676 $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association');
677 foreach ($expected as $workspace_id => $expected_tracked_revision_ids) {
678 $tracked_entities = $workspace_association_storage->getTrackedEntities($workspace_id, TRUE);
679 $tracked_revision_ids = isset($tracked_entities[$entity_type_id]) ? $tracked_entities[$entity_type_id] : [];
680 $this->assertEquals($expected_tracked_revision_ids, array_keys($tracked_revision_ids));
685 * Sets a given workspace as active.
687 * @param string $workspace_id
688 * The ID of the workspace to switch to.
690 protected function switchToWorkspace($workspace_id) {
691 // Switch the test runner's context to the specified workspace.
692 $workspace = $this->entityTypeManager->getStorage('workspace')->load($workspace_id);
693 \Drupal::service('workspaces.manager')->setActiveWorkspace($workspace);
697 * Flattens the expectations array defined by testWorkspaces().
699 * @param array $expected
700 * An array as defined by testWorkspaces().
701 * @param string $entity_type_id
702 * The ID of the entity type that is being tested.
705 * An array where all the entity IDs and revision IDs are merged inside each
706 * expected values array.
708 protected function flattenExpectedValues(array $expected, $entity_type_id) {
711 $entity_keys = $this->entityTypeManager->getDefinition($entity_type_id)->getKeys();
712 foreach ($expected as $workspace_id => $workspace_values) {
713 foreach ($workspace_values as $entity_id => $entity_revisions) {
714 foreach ($entity_revisions as $revision_id => $revision_values) {
715 $flattened[$workspace_id][] = [$entity_keys['id'] => $entity_id, $entity_keys['revision'] => $revision_id] + $revision_values;
724 * Tests that entity forms can be stored in the form cache.
726 public function testFormCacheForEntityForms() {
727 $this->initializeWorkspacesModule();
728 $this->switchToWorkspace('stage');
730 $form_builder = $this->container->get('form_builder');
732 $form = $this->entityTypeManager->getFormObject('entity_test_mulrevpub', 'default');
733 $form->setEntity(EntityTestMulRevPub::create([]));
735 $form_state = new FormState();
736 $built_form = $form_builder->buildForm($form, $form_state);
737 $form_builder->setCache($built_form['#build_id'], $built_form, $form_state);
741 * Tests that non-entity forms can be stored in the form cache.
743 public function testFormCacheForRegularForms() {
744 $this->initializeWorkspacesModule();
745 $this->switchToWorkspace('stage');
747 $form_builder = $this->container->get('form_builder');
749 $form_state = new FormState();
750 $built_form = $form_builder->getForm(SiteInformationForm::class, $form_state);
751 $form_builder->setCache($built_form['#build_id'], $built_form, $form_state);