Initial Drupal 11 with DDEV setup
This commit is contained in:
90
web/core/modules/workspaces/tests/fixtures/update/workspaces.php
vendored
Normal file
90
web/core/modules/workspaces/tests/fixtures/update/workspaces.php
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
|
||||
$connection = Database::getConnection();
|
||||
|
||||
// Set the schema version.
|
||||
$connection->merge('key_value')
|
||||
->fields([
|
||||
'value' => 'i:10000;',
|
||||
'name' => 'workspaces',
|
||||
'collection' => 'system.schema',
|
||||
])
|
||||
->condition('collection', 'system.schema')
|
||||
->condition('name', 'workspaces')
|
||||
->execute();
|
||||
|
||||
// Update core.extension.
|
||||
$extensions = $connection->select('config')
|
||||
->fields('config', ['data'])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.extension')
|
||||
->execute()
|
||||
->fetchField();
|
||||
$extensions = unserialize($extensions);
|
||||
$extensions['module']['workspaces'] = 0;
|
||||
$connection->update('config')
|
||||
->fields(['data' => serialize($extensions)])
|
||||
->condition('collection', '')
|
||||
->condition('name', 'core.extension')
|
||||
->execute();
|
||||
|
||||
// Add all workspaces_removed_post_updates() as existing updates.
|
||||
require_once __DIR__ . '/../../../../workspaces/workspaces.post_update.php';
|
||||
$existing_updates = $connection->select('key_value')
|
||||
->fields('key_value', ['value'])
|
||||
->condition('collection', 'post_update')
|
||||
->condition('name', 'existing_updates')
|
||||
->execute()
|
||||
->fetchField();
|
||||
$existing_updates = unserialize($existing_updates);
|
||||
$existing_updates = array_merge(
|
||||
$existing_updates,
|
||||
array_keys(workspaces_removed_post_updates())
|
||||
);
|
||||
$connection->update('key_value')
|
||||
->fields(['value' => serialize($existing_updates)])
|
||||
->condition('collection', 'post_update')
|
||||
->condition('name', 'existing_updates')
|
||||
->execute();
|
||||
|
||||
// Create the 'workspace_association' table.
|
||||
$spec = [
|
||||
'description' => 'Stores the association between entity revisions and their workspace.',
|
||||
'fields' => [
|
||||
'workspace' => [
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'The workspace ID.',
|
||||
],
|
||||
'target_entity_type_id' => [
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => EntityTypeInterface::ID_MAX_LENGTH,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'The ID of the associated entity type.',
|
||||
],
|
||||
'target_entity_id' => [
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'description' => 'The ID of the associated entity.',
|
||||
],
|
||||
'target_entity_revision_id' => [
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'description' => 'The revision ID of the associated entity.',
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
'target_entity_revision_id' => ['target_entity_revision_id'],
|
||||
],
|
||||
'primary key' => ['workspace', 'target_entity_type_id', 'target_entity_id'],
|
||||
];
|
||||
$connection->schema()->createTable('workspace_association', $spec);
|
||||
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\workspace_access_test\Hook;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Access\AccessResultInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for workspace_access_test.
|
||||
*/
|
||||
class WorkspaceAccessTestHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_access() for the 'workspace' entity type.
|
||||
*/
|
||||
#[Hook('workspace_access')]
|
||||
public function workspaceAccess(EntityInterface $entity, $operation, AccountInterface $account): AccessResultInterface {
|
||||
return \Drupal::state()->get("workspace_access_test.result.{$operation}", AccessResult::neutral());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
name: 'Workspace Access Test'
|
||||
type: module
|
||||
description: 'Provides supporting code for testing access for workspaces.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:workspaces
|
||||
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\workspace_update_test\Negotiator;
|
||||
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
use Drupal\workspaces\Negotiator\WorkspaceIdNegotiatorInterface;
|
||||
use Drupal\workspaces\Negotiator\WorkspaceNegotiatorInterface;
|
||||
use Drupal\workspaces\WorkspaceInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Defines a workspace negotiator used for testing.
|
||||
*/
|
||||
class TestWorkspaceNegotiator implements WorkspaceNegotiatorInterface, WorkspaceIdNegotiatorInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies(Request $request) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getActiveWorkspaceId(Request $request): ?string {
|
||||
return 'test';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getActiveWorkspace(Request $request) {
|
||||
return Workspace::load($this->getActiveWorkspaceId($request));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setActiveWorkspace(WorkspaceInterface $workspace) {
|
||||
// Nothing to do here.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function unsetActiveWorkspace() {
|
||||
// Nothing to do here.
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
name: 'Workspace Update Test'
|
||||
type: module
|
||||
description: 'Provides supporting code for testing workspaces during database updates.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:workspaces
|
||||
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Post update functions for the Workspace Update Test module.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Checks the active workspace during database updates.
|
||||
*/
|
||||
function workspace_update_test_post_update_check_active_workspace(): void {
|
||||
\Drupal::state()->set('workspace_update_test.has_active_workspace', \Drupal::service('workspaces.manager')->hasActiveWorkspace());
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
services:
|
||||
workspace_update_test.negotiator.test:
|
||||
class: Drupal\workspace_update_test\Negotiator\TestWorkspaceNegotiator
|
||||
tags:
|
||||
- { name: workspace_negotiator, priority: 0 }
|
||||
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\workspaces_test\Entity;
|
||||
|
||||
use Drupal\Core\Entity\Attribute\ContentEntityType;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\entity_test\Entity\EntityTestMulRevPub;
|
||||
|
||||
/**
|
||||
* Defines the test entity class.
|
||||
*/
|
||||
#[ContentEntityType(
|
||||
id: 'entity_test_mulrevpub_string_id',
|
||||
label: new TranslatableMarkup('Test entity - revisions, data table, and published interface'),
|
||||
base_table: 'entity_test_mulrevpub_string_id',
|
||||
data_table: 'entity_test_mulrevpub_string_id_property_data',
|
||||
revision_table: 'entity_test_mulrevpub_string_id_revision',
|
||||
revision_data_table: 'entity_test_mulrevpub_string_id_property_revision',
|
||||
admin_permission: 'administer entity_test content',
|
||||
translatable: TRUE,
|
||||
entity_keys: [
|
||||
'id' => 'id',
|
||||
'uuid' => 'uuid',
|
||||
'bundle' => 'type',
|
||||
'revision' => 'revision_id',
|
||||
'label' => 'name',
|
||||
'langcode' => 'langcode',
|
||||
'published' => 'status',
|
||||
]
|
||||
)]
|
||||
class EntityTestMulRevPubStringId extends EntityTestMulRevPub {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
$fields['id'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('ID'))
|
||||
->setDescription(t('The ID of the test entity.'))
|
||||
->setReadOnly(TRUE)
|
||||
// In order to work around the InnoDB 191 character limit on utf8mb4
|
||||
// primary keys, we set the character set for the field to ASCII.
|
||||
->setSetting('is_ascii', TRUE);
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\workspaces_test;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\workspaces\Entity\Handler\DefaultWorkspaceHandler;
|
||||
|
||||
/**
|
||||
* Provides a custom workspace handler for testing purposes.
|
||||
*/
|
||||
class EntityTestRevPubWorkspaceHandler extends DefaultWorkspaceHandler {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEntitySupported(EntityInterface $entity): bool {
|
||||
return $entity->bundle() !== 'ignored_bundle';
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\workspaces_test\Form;
|
||||
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Form\WorkspaceSafeFormInterface;
|
||||
use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\workspaces\WorkspaceManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Form for testing the active workspace.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ActiveWorkspaceTestForm extends FormBase implements WorkspaceSafeFormInterface {
|
||||
|
||||
/**
|
||||
* The workspace manager.
|
||||
*/
|
||||
protected WorkspaceManagerInterface $workspaceManager;
|
||||
|
||||
/**
|
||||
* The test key-value store.
|
||||
*/
|
||||
protected KeyValueStoreInterface $keyValue;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container): static {
|
||||
$instance = parent::create($container);
|
||||
$instance->workspaceManager = $container->get('workspaces.manager');
|
||||
$instance->keyValue = $container->get('keyvalue')->get('ws_test');
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId(): string {
|
||||
return 'active_workspace_test_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state): array {
|
||||
$form['test'] = [
|
||||
'#type' => 'textfield',
|
||||
'#ajax' => [
|
||||
'url' => Url::fromRoute('workspaces_test.get_form'),
|
||||
'callback' => function () {
|
||||
$this->keyValue->set('ajax_test_active_workspace', $this->workspaceManager->getActiveWorkspace()->id());
|
||||
return new AjaxResponse();
|
||||
},
|
||||
],
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state): void {
|
||||
$this->keyValue->set('form_test_active_workspace', $this->workspaceManager->getActiveWorkspace()->id());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\workspaces_test\Hook;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
|
||||
/**
|
||||
* Hook implementations for workspaces_test.
|
||||
*/
|
||||
class WorkspacesTestHooks {
|
||||
|
||||
public function __construct(
|
||||
#[Autowire(service: 'keyvalue')]
|
||||
protected readonly KeyValueFactoryInterface $keyValueFactory,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_type_alter().
|
||||
*/
|
||||
#[Hook('entity_type_alter')]
|
||||
public function entityTypeAlter(array &$entity_types) : void {
|
||||
$state = \Drupal::state();
|
||||
// Allow all entity types to have their definition changed dynamically for
|
||||
// testing purposes.
|
||||
foreach ($entity_types as $entity_type_id => $entity_type) {
|
||||
$entity_types[$entity_type_id] = $state->get("{$entity_type_id}.entity_type", $entity_types[$entity_type_id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_translation_create() for 'entity_test_mulrevpub'.
|
||||
*/
|
||||
#[Hook('entity_test_mulrevpub_translation_create')]
|
||||
public function entityTranslationCreate(): void {
|
||||
/** @var \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager */
|
||||
$workspace_manager = \Drupal::service('workspaces.manager');
|
||||
$this->keyValueFactory->get('ws_test')->set('workspace_was_active', $workspace_manager->hasActiveWorkspace());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_create().
|
||||
*/
|
||||
#[Hook('entity_create')]
|
||||
public function entityCreate(EntityInterface $entity): void {
|
||||
$this->incrementHookCount('hook_entity_create', $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_presave().
|
||||
*/
|
||||
#[Hook('entity_presave')]
|
||||
public function entityPresave(EntityInterface $entity): void {
|
||||
$this->incrementHookCount('hook_entity_presave', $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_insert().
|
||||
*/
|
||||
#[Hook('entity_insert')]
|
||||
public function entityInsert(EntityInterface $entity): void {
|
||||
$this->incrementHookCount('hook_entity_insert', $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_update().
|
||||
*/
|
||||
#[Hook('entity_update')]
|
||||
public function entityUpdate(EntityInterface $entity): void {
|
||||
$this->incrementHookCount('hook_entity_update', $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the invocation count for a specific entity hook.
|
||||
*
|
||||
* @param string $hook_name
|
||||
* The name of the hook being invoked (e.g., 'hook_entity_create').
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity object involved in the hook.
|
||||
*/
|
||||
protected function incrementHookCount(string $hook_name, EntityInterface $entity): void {
|
||||
$key = $entity->getEntityTypeId() . '.' . $hook_name . '.count';
|
||||
$count = $this->keyValueFactory->get('ws_test')->get($key, 0);
|
||||
$this->keyValueFactory->get('ws_test')->set($key, $count + 1);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
name: 'Workspace Test'
|
||||
type: module
|
||||
description: 'Provides supporting code for testing workspaces.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:entity_test
|
||||
- drupal:workspaces
|
||||
@ -0,0 +1,7 @@
|
||||
workspaces_test.get_form:
|
||||
path: '/active-workspace-test-form'
|
||||
defaults:
|
||||
_title: 'Active Workspace Test Form'
|
||||
_form: '\Drupal\workspaces_test\Form\ActiveWorkspaceTestForm'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional;
|
||||
|
||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
|
||||
|
||||
/**
|
||||
* Generic module test for workspaces.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class GenericTest extends GenericModuleTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function preUninstallSteps(): void {
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('workspace');
|
||||
$workspaces = $storage->loadMultiple();
|
||||
$storage->delete($workspaces);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,361 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\content_translation\Traits\ContentTranslationTestTrait;
|
||||
use Drupal\Tests\WaitTerminateTestTrait;
|
||||
|
||||
/**
|
||||
* Tests path aliases with workspaces.
|
||||
*
|
||||
* @group path
|
||||
* @group workspaces
|
||||
*/
|
||||
class PathWorkspacesTest extends BrowserTestBase {
|
||||
|
||||
use ContentTranslationTestTrait;
|
||||
use WorkspaceTestUtilities;
|
||||
use WaitTerminateTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'block',
|
||||
'content_translation',
|
||||
'node',
|
||||
'path',
|
||||
'workspaces',
|
||||
'workspaces_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
static::createLanguageFromLangcode('ro');
|
||||
$this->rebuildContainer();
|
||||
|
||||
// Create a content type.
|
||||
$this->drupalCreateContentType([
|
||||
'name' => 'article',
|
||||
'type' => 'article',
|
||||
]);
|
||||
|
||||
$permissions = [
|
||||
'administer languages',
|
||||
'administer nodes',
|
||||
'administer url aliases',
|
||||
'administer workspaces',
|
||||
'create article content',
|
||||
'create content translations',
|
||||
'edit any article content',
|
||||
'translate any entity',
|
||||
];
|
||||
$this->drupalLogin($this->drupalCreateUser($permissions));
|
||||
|
||||
// Enable URL language detection and selection.
|
||||
$edit = ['language_interface[enabled][language-url]' => 1];
|
||||
$this->drupalGet('admin/config/regional/language/detection');
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
|
||||
// Enable translation for article node.
|
||||
static::enableContentTranslation('node', 'article');
|
||||
|
||||
$this->setupWorkspaceSwitcherBlock();
|
||||
|
||||
// The \Drupal\path_alias\AliasPrefixList service performs cache clears
|
||||
// after Drupal has flushed the response to the client. We use
|
||||
// WaitTerminateTestTrait to wait for Drupal to do this before continuing.
|
||||
$this->setWaitForTerminate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests path aliases with workspaces.
|
||||
*/
|
||||
public function testPathAliases(): void {
|
||||
// Create a published node in Live, without an alias.
|
||||
$node = $this->drupalCreateNode([
|
||||
'type' => 'article',
|
||||
'status' => TRUE,
|
||||
]);
|
||||
|
||||
// Activate a workspace and create an alias for the node.
|
||||
$stage = $this->createAndActivateWorkspaceThroughUi('Stage', 'stage');
|
||||
|
||||
$edit = [
|
||||
'path[0][alias]' => '/' . $this->randomMachineName(),
|
||||
];
|
||||
$this->drupalGet('node/' . $node->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Check that the node can be accessed in Stage with the given alias.
|
||||
$path = $edit['path[0][alias]'];
|
||||
$this->assertAccessiblePaths([$path]);
|
||||
|
||||
// Check that the 'preload-paths' cache includes the active workspace ID in
|
||||
// the cache key.
|
||||
$this->assertNotEmpty(\Drupal::cache('data')->get('preload-paths:stage:/node/1'));
|
||||
$this->assertFalse(\Drupal::cache('data')->get('preload-paths:/node/1'));
|
||||
|
||||
// Check that the alias can not be accessed in Live.
|
||||
$this->switchToLive();
|
||||
$this->assertNotAccessiblePaths([$path]);
|
||||
$this->assertFalse(\Drupal::cache('data')->get('preload-paths:/node/1'));
|
||||
|
||||
// Publish the workspace and check that the alias can be accessed in Live.
|
||||
$stage->publish();
|
||||
$this->assertAccessiblePaths([$path]);
|
||||
$this->assertNotEmpty(\Drupal::cache('data')->get('preload-paths:/node/1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests path aliases with workspaces and user switching.
|
||||
*/
|
||||
public function testPathAliasesUserSwitch(): void {
|
||||
// Create a published node in Live, without an alias.
|
||||
$node = $this->drupalCreateNode([
|
||||
'type' => 'article',
|
||||
'status' => TRUE,
|
||||
]);
|
||||
|
||||
// Activate a workspace and create an alias for the node.
|
||||
$stage = $this->createAndActivateWorkspaceThroughUi('Stage', 'stage');
|
||||
|
||||
$edit = [
|
||||
'path[0][alias]' => '/' . $this->randomMachineName(),
|
||||
];
|
||||
$this->drupalGet('node/' . $node->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Check that the node can be accessed in Stage with the given alias.
|
||||
$path = $edit['path[0][alias]'];
|
||||
$this->assertAccessiblePaths([$path]);
|
||||
|
||||
// Check that the 'preload-paths' cache includes the active workspace ID in
|
||||
// the cache key.
|
||||
$this->assertNotEmpty(\Drupal::cache('data')->get('preload-paths:stage:/node/1'));
|
||||
$this->assertFalse(\Drupal::cache('data')->get('preload-paths:/node/1'));
|
||||
|
||||
// Check that the alias can not be accessed in Live, by logging out without
|
||||
// an explicit switch.
|
||||
$this->drupalLogout();
|
||||
$this->assertNotAccessiblePaths([$path]);
|
||||
$this->assertFalse(\Drupal::cache('data')->get('preload-paths:/node/1'));
|
||||
|
||||
// Publish the workspace and check that the alias can be accessed in Live.
|
||||
$this->drupalLogin($this->rootUser);
|
||||
$stage->publish();
|
||||
|
||||
$this->drupalLogout();
|
||||
$this->assertAccessiblePaths([$path]);
|
||||
$this->assertNotEmpty(\Drupal::cache('data')->get('preload-paths:/node/1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests path aliases with workspaces for translatable nodes.
|
||||
*/
|
||||
public function testPathAliasesWithTranslation(): void {
|
||||
$stage = $this->createWorkspaceThroughUi('Stage', 'stage');
|
||||
|
||||
// Create one node with a random alias.
|
||||
$default_node = $this->drupalCreateNode([
|
||||
'type' => 'article',
|
||||
'langcode' => 'en',
|
||||
'status' => TRUE,
|
||||
'path' => '/' . $this->randomMachineName(),
|
||||
]);
|
||||
|
||||
// Add published translation with another alias.
|
||||
$this->drupalGet('node/' . $default_node->id());
|
||||
$this->drupalGet('node/' . $default_node->id() . '/translations');
|
||||
$this->clickLink('Add');
|
||||
$edit_translation = [
|
||||
'body[0][value]' => $this->randomMachineName(),
|
||||
'status[value]' => TRUE,
|
||||
'path[0][alias]' => '/' . $this->randomMachineName(),
|
||||
];
|
||||
$this->submitForm($edit_translation, 'Save (this translation)');
|
||||
// Confirm that the alias works.
|
||||
$this->drupalGet('ro' . $edit_translation['path[0][alias]']);
|
||||
$this->assertSession()->pageTextContains($edit_translation['body[0][value]']);
|
||||
|
||||
$default_path = $default_node->path->alias;
|
||||
$translation_path = 'ro' . $edit_translation['path[0][alias]'];
|
||||
|
||||
$this->assertAccessiblePaths([$default_path, $translation_path]);
|
||||
|
||||
// Verify the default alias is available in the live workspace.
|
||||
$this->assertAccessiblePaths([$default_path]);
|
||||
|
||||
$this->switchToWorkspace($stage);
|
||||
|
||||
$this->assertAccessiblePaths([$default_path, $translation_path]);
|
||||
|
||||
// Create a workspace-specific revision for the translation with a new path
|
||||
// alias.
|
||||
$edit_new_translation_draft_with_alias = [
|
||||
'path[0][alias]' => '/' . $this->randomMachineName(),
|
||||
];
|
||||
$this->drupalGet('ro/node/' . $default_node->id() . '/edit');
|
||||
$this->submitForm($edit_new_translation_draft_with_alias, 'Save (this translation)');
|
||||
$stage_translation_path = 'ro' . $edit_new_translation_draft_with_alias['path[0][alias]'];
|
||||
|
||||
// The new alias of the translation should be available in Stage, but not
|
||||
// available in Live.
|
||||
$this->assertAccessiblePaths([$default_path, $stage_translation_path]);
|
||||
|
||||
// Check that the previous (Live) path alias no longer works.
|
||||
$this->assertNotAccessiblePaths([$translation_path]);
|
||||
|
||||
// Switch out of Stage and check that the initial path aliases still work.
|
||||
$this->switchToLive();
|
||||
$this->assertAccessiblePaths([$default_path, $translation_path]);
|
||||
$this->assertNotAccessiblePaths([$stage_translation_path]);
|
||||
|
||||
// Switch back to Stage.
|
||||
$this->switchToWorkspace($stage);
|
||||
|
||||
// Create new workspace-specific revision for translation without changing
|
||||
// the path alias.
|
||||
$edit_new_translation_draft = [
|
||||
'body[0][value]' => $this->randomMachineName(),
|
||||
];
|
||||
$this->drupalGet('ro/node/' . $default_node->id() . '/edit');
|
||||
$this->submitForm($edit_new_translation_draft, 'Save (this translation)');
|
||||
// Confirm that the new draft revision was created.
|
||||
$this->assertSession()->pageTextContains($edit_new_translation_draft['body[0][value]']);
|
||||
|
||||
// Switch out of Stage and check that the initial path aliases still work.
|
||||
$this->switchToLive();
|
||||
$this->assertAccessiblePaths([$default_path, $translation_path]);
|
||||
$this->assertNotAccessiblePaths([$stage_translation_path]);
|
||||
|
||||
// Switch back to Stage.
|
||||
$this->switchToWorkspace($stage);
|
||||
$this->assertAccessiblePaths([$default_path, $stage_translation_path]);
|
||||
$this->assertNotAccessiblePaths([$translation_path]);
|
||||
|
||||
// Create a new workspace-specific revision for translation with path alias
|
||||
// from the original language's default revision.
|
||||
$edit_new_translation_draft_with_defaults_alias = [
|
||||
'path[0][alias]' => $default_node->path->alias,
|
||||
];
|
||||
$this->drupalGet('ro/node/' . $default_node->id() . '/edit');
|
||||
$this->submitForm($edit_new_translation_draft_with_defaults_alias, 'Save (this translation)');
|
||||
|
||||
// Switch out of Stage and check that the initial path aliases still work.
|
||||
$this->switchToLive();
|
||||
$this->assertAccessiblePaths([$default_path, $translation_path]);
|
||||
$this->assertNotAccessiblePaths([$stage_translation_path]);
|
||||
|
||||
// Check that only one path alias (the original one) is available in Stage.
|
||||
$this->switchToWorkspace($stage);
|
||||
$this->assertAccessiblePaths([$default_path]);
|
||||
$this->assertNotAccessiblePaths([$translation_path, $stage_translation_path]);
|
||||
|
||||
// Create new workspace-specific revision for translation with a deleted
|
||||
// (empty) path alias.
|
||||
$edit_new_translation_draft_empty_alias = [
|
||||
'body[0][value]' => $this->randomMachineName(),
|
||||
'path[0][alias]' => '',
|
||||
];
|
||||
$this->drupalGet('ro/node/' . $default_node->id() . '/edit');
|
||||
$this->submitForm($edit_new_translation_draft_empty_alias, 'Save (this translation)');
|
||||
|
||||
// Check that only one path alias (the original one) is available now.
|
||||
$this->switchToLive();
|
||||
$this->assertAccessiblePaths([$default_path, $translation_path]);
|
||||
$this->assertNotAccessiblePaths([$stage_translation_path]);
|
||||
|
||||
$this->switchToWorkspace($stage);
|
||||
$this->assertAccessiblePaths([$default_path]);
|
||||
$this->assertNotAccessiblePaths([$translation_path, $stage_translation_path]);
|
||||
|
||||
// Create a new workspace-specific revision for the translation with a new
|
||||
// path alias.
|
||||
$edit_new_translation = [
|
||||
'body[0][value]' => $this->randomMachineName(),
|
||||
'path[0][alias]' => '/' . $this->randomMachineName(),
|
||||
];
|
||||
$this->drupalGet('ro/node/' . $default_node->id() . '/edit');
|
||||
$this->submitForm($edit_new_translation, 'Save (this translation)');
|
||||
|
||||
// Confirm that the new revision was created.
|
||||
$this->assertSession()->pageTextContains($edit_new_translation['body[0][value]']);
|
||||
$this->assertSession()->addressEquals('ro' . $edit_new_translation['path[0][alias]']);
|
||||
|
||||
// Check that only the new path alias of the translation can be accessed.
|
||||
$new_stage_translation_path = 'ro' . $edit_new_translation['path[0][alias]'];
|
||||
$this->assertAccessiblePaths([$default_path, $new_stage_translation_path]);
|
||||
$this->assertNotAccessiblePaths([$stage_translation_path]);
|
||||
|
||||
// Switch out of Stage and check that none of the workspace-specific path
|
||||
// aliases can be accessed.
|
||||
$this->switchToLive();
|
||||
$this->assertAccessiblePaths([$default_path, $translation_path]);
|
||||
$this->assertNotAccessiblePaths([$stage_translation_path, $new_stage_translation_path]);
|
||||
|
||||
// Publish Stage and check that its path alias for the translation can be
|
||||
// accessed.
|
||||
$stage->publish();
|
||||
$this->assertAccessiblePaths([$default_path, $new_stage_translation_path]);
|
||||
$this->assertNotAccessiblePaths([$stage_translation_path]);
|
||||
|
||||
// Switch back to Stage.
|
||||
$this->switchToWorkspace($stage);
|
||||
|
||||
// Edit the path alias to set its language to "Not specified".
|
||||
$alias_edit_path = "admin/config/search/path/edit/{$default_node->id()}";
|
||||
$this->drupalGet($alias_edit_path);
|
||||
// Set the alias language to "Not specified".
|
||||
$edit = [
|
||||
'langcode[0][value]' => 'und',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Verify the path alias is still available in the Stage workspace.
|
||||
$this->assertAccessiblePaths([$default_path]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper callback to verify paths are responding with status 200.
|
||||
*
|
||||
* @param string[] $paths
|
||||
* An array of paths to check for.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertAccessiblePaths(array $paths): void {
|
||||
foreach ($paths as $path) {
|
||||
$this->drupalGet($path);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper callback to verify paths are responding with status 404.
|
||||
*
|
||||
* @param string[] $paths
|
||||
* An array of paths to check for.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertNotAccessiblePaths(array $paths): void {
|
||||
foreach ($paths as $path) {
|
||||
$this->drupalGet($path);
|
||||
$this->assertSession()->statusCodeEquals(404);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* Test workspace entities for unauthenticated JSON requests.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceJsonAnonTest extends WorkspaceResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* Test workspace entities for JSON requests via basic auth.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceJsonBasicAuthTest extends WorkspaceResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* Test workspace entities for JSON requests with cookie authentication.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceJsonCookieTest extends WorkspaceResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Base class for workspace EntityResource tests.
|
||||
*/
|
||||
abstract class WorkspaceResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['workspaces'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'workspace';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $patchProtectedFieldNames = [
|
||||
'changed' => NULL,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $uniqueFieldNames = [
|
||||
'id',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $firstCreatedEntityId = 'running_on_faith';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $secondCreatedEntityId = 'running_on_faith_2';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
$this->grantPermissionsToTestedRole(['view any workspace']);
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
$this->grantPermissionsToTestedRole(['create workspace']);
|
||||
break;
|
||||
|
||||
case 'PATCH':
|
||||
$this->grantPermissionsToTestedRole(['edit any workspace']);
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
$this->grantPermissionsToTestedRole(['delete any workspace']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$workspace = Workspace::create([
|
||||
'id' => 'layla',
|
||||
'label' => 'Layla',
|
||||
]);
|
||||
$workspace->save();
|
||||
return $workspace;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createAnotherEntity() {
|
||||
$workspace = $this->entity->createDuplicate();
|
||||
$workspace->id = 'layla_dupe';
|
||||
$workspace->label = 'Layla_dupe';
|
||||
$workspace->save();
|
||||
return $workspace;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
$author = User::load($this->entity->getOwnerId());
|
||||
return [
|
||||
'created' => [
|
||||
[
|
||||
'value' => (new \DateTime())->setTimestamp((int) $this->entity->getCreatedTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
|
||||
'format' => \DateTime::RFC3339,
|
||||
],
|
||||
],
|
||||
'changed' => [
|
||||
[
|
||||
'value' => (new \DateTime())->setTimestamp($this->entity->getChangedTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
|
||||
'format' => \DateTime::RFC3339,
|
||||
],
|
||||
],
|
||||
'id' => [
|
||||
[
|
||||
'value' => 'layla',
|
||||
],
|
||||
],
|
||||
'label' => [
|
||||
[
|
||||
'value' => 'Layla',
|
||||
],
|
||||
],
|
||||
'revision_id' => [
|
||||
[
|
||||
'value' => 1,
|
||||
],
|
||||
],
|
||||
'parent' => [],
|
||||
'uid' => [
|
||||
[
|
||||
'target_id' => (int) $author->id(),
|
||||
'target_type' => 'user',
|
||||
'target_uuid' => $author->uuid(),
|
||||
'url' => base_path() . 'user/' . $author->id(),
|
||||
],
|
||||
],
|
||||
'uuid' => [
|
||||
[
|
||||
'value' => $this->entity->uuid(),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
return [
|
||||
'id' => [
|
||||
[
|
||||
'value' => static::$firstCreatedEntityId,
|
||||
],
|
||||
],
|
||||
'label' => [
|
||||
[
|
||||
'value' => 'Running on faith',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPatchEntity() {
|
||||
return array_diff_key($this->getNormalizedPostEntity(), ['id' => TRUE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedUnauthorizedAccessMessage($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
return "The 'view any workspace' permission is required.";
|
||||
|
||||
case 'POST':
|
||||
return "The following permissions are required: 'administer workspaces' OR 'create workspace'.";
|
||||
|
||||
case 'PATCH':
|
||||
return "The 'edit any workspace' permission is required.";
|
||||
|
||||
case 'DELETE':
|
||||
return "The 'delete any workspace' permission is required.";
|
||||
|
||||
}
|
||||
return parent::getExpectedUnauthorizedAccessMessage($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getModifiedEntityForPostTesting() {
|
||||
$modified = parent::getModifiedEntityForPostTesting();
|
||||
// Even though the field type of the workspace ID is 'string', it acts as a
|
||||
// machine name through a custom constraint, so we need to ensure that we
|
||||
// generate a proper random value for it.
|
||||
// @see \Drupal\workspaces\Entity\Workspace::baseFieldDefinitions()
|
||||
$modified['id'] = [$this->randomMachineName()];
|
||||
return $modified;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* Test workspace entities for unauthenticated XML requests.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceXmlAnonTest extends WorkspaceResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* Test workspace entities for XML requests with cookie authentication.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceXmlBasicAuthTest extends WorkspaceResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* Test workspace entities for XML requests.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceXmlCookieTest extends WorkspaceResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional\Update;
|
||||
|
||||
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests the update path for string IDs in workspace_association.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceAssociationStringIdsUpdatePathTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $checkEntityFieldDefinitionUpdates = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles(): void {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-10.3.0.bare.standard.php.gz',
|
||||
__DIR__ . '/../../../fixtures/update/workspaces.php',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the update path for string IDs in workspace_association.
|
||||
*/
|
||||
public function testRunUpdates(): void {
|
||||
$schema = \Drupal::database()->schema();
|
||||
$find_primary_key_columns = new \ReflectionMethod(get_class($schema), 'findPrimaryKeyColumns');
|
||||
|
||||
$this->assertFalse($schema->fieldExists('workspace_association', 'target_entity_id_string'));
|
||||
$primary_key_columns = ['workspace', 'target_entity_type_id', 'target_entity_id'];
|
||||
$this->assertEquals($primary_key_columns, $find_primary_key_columns->invoke($schema, 'workspace_association'));
|
||||
|
||||
$this->runUpdates();
|
||||
|
||||
$this->assertTrue($schema->fieldExists('workspace_association', 'target_entity_id_string'));
|
||||
$primary_key_columns = ['workspace', 'target_entity_type_id', 'target_entity_id', 'target_entity_id_string'];
|
||||
$this->assertEquals($primary_key_columns, $find_primary_key_columns->invoke($schema, 'workspace_association'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional\UpdateSystem;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\UpdatePathTestTrait;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Tests that there is no active workspace during database updates.
|
||||
*
|
||||
* @group workspaces
|
||||
* @group Update
|
||||
*/
|
||||
class ActiveWorkspaceUpdateTest extends BrowserTestBase {
|
||||
|
||||
use UpdatePathTestTrait;
|
||||
use UserCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['workspaces'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpCurrentUser([], ['view any workspace']);
|
||||
$this->container->get('module_installer')->install(['workspace_update_test']);
|
||||
$this->rebuildContainer();
|
||||
|
||||
// Ensure the workspace_update_test_post_update_check_active_workspace()
|
||||
// update runs.
|
||||
$existing_updates = \Drupal::keyValue('post_update')->get('existing_updates', []);
|
||||
$index = array_search('workspace_update_test_post_update_check_active_workspace', $existing_updates);
|
||||
unset($existing_updates[$index]);
|
||||
\Drupal::keyValue('post_update')->set('existing_updates', $existing_updates);
|
||||
|
||||
// Create a valid workspace that can be used for testing.
|
||||
Workspace::create(['id' => 'test', 'label' => 'Test'])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that there is no active workspace during database updates.
|
||||
*/
|
||||
public function testActiveWorkspaceDuringUpdate(): void {
|
||||
/** @var \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager */
|
||||
$workspace_manager = \Drupal::service('workspaces.manager');
|
||||
|
||||
// Check that we have an active workspace before running the updates.
|
||||
$this->assertTrue($workspace_manager->hasActiveWorkspace());
|
||||
$this->assertEquals('test', $workspace_manager->getActiveWorkspace()->id());
|
||||
|
||||
$this->runUpdates();
|
||||
|
||||
// Check that we didn't have an active workspace while running the updates.
|
||||
// @see workspace_update_test_post_update_check_active_workspace()
|
||||
$this->assertFalse(\Drupal::state()->get('workspace_update_test.has_active_workspace'));
|
||||
|
||||
// Check that we have an active workspace after running the updates.
|
||||
$workspace_manager = \Drupal::service('workspaces.manager');
|
||||
$this->assertTrue($workspace_manager->hasActiveWorkspace());
|
||||
$this->assertEquals('test', $workspace_manager->getActiveWorkspace()->id());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
|
||||
// cspell:ignore ditka
|
||||
|
||||
/**
|
||||
* Tests access bypass permission controls on workspaces.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceBypassTest extends BrowserTestBase {
|
||||
|
||||
use WorkspaceTestUtilities;
|
||||
use ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'user', 'block', 'workspaces', 'workspaces_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Verifies that a user can edit anything in a workspace they own.
|
||||
*/
|
||||
public function testBypassOwnWorkspace(): void {
|
||||
$permissions = [
|
||||
'create workspace',
|
||||
'edit own workspace',
|
||||
'view own workspace',
|
||||
'bypass entity access own workspace',
|
||||
];
|
||||
|
||||
$this->createContentType(['type' => 'test', 'label' => 'Test']);
|
||||
$this->setupWorkspaceSwitcherBlock();
|
||||
|
||||
$coach = $this->drupalCreateUser(array_merge($permissions, ['create test content']));
|
||||
|
||||
// Login as a limited-access user and create a workspace.
|
||||
$this->drupalLogin($coach);
|
||||
$bears = $this->createAndActivateWorkspaceThroughUi('Bears', 'bears');
|
||||
|
||||
// Now create a node in the Bears workspace, as the owner of that workspace.
|
||||
$coach_bears_node = $this->createNodeThroughUi('Ditka Bears node', 'test');
|
||||
$coach_bears_node_id = $coach_bears_node->id();
|
||||
|
||||
// Editing both nodes should be possible.
|
||||
$this->drupalGet('/node/' . $coach_bears_node_id . '/edit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Create a new user that should be able to edit anything in the Bears
|
||||
// workspace.
|
||||
$this->switchToLive();
|
||||
$lombardi = $this->drupalCreateUser(array_merge($permissions, ['view any workspace']));
|
||||
$this->drupalLogin($lombardi);
|
||||
$this->switchToWorkspace($bears);
|
||||
|
||||
// Editor 2 has the bypass permission but does not own the workspace and so,
|
||||
// should not be able to create and edit any node.
|
||||
$this->drupalGet('/node/' . $coach_bears_node_id . '/edit');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
use Drupal\workspaces\WorkspaceCacheContext;
|
||||
|
||||
/**
|
||||
* Tests the workspace cache context.
|
||||
*
|
||||
* @group workspaces
|
||||
* @group Cache
|
||||
*/
|
||||
class WorkspaceCacheContextTest extends BrowserTestBase {
|
||||
|
||||
use AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block', 'node', 'workspaces', 'workspaces_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the 'workspace' cache context.
|
||||
*/
|
||||
public function testWorkspaceCacheContext(): void {
|
||||
$renderer = \Drupal::service('renderer');
|
||||
$cache_contexts_manager = \Drupal::service("cache_contexts_manager");
|
||||
/** @var \Drupal\Core\Cache\VariationCacheFactoryInterface $variation_cache_factory */
|
||||
$variation_cache_factory = $this->container->get('variation_cache_factory');
|
||||
|
||||
// Check that the 'workspace' cache context is present when the module is
|
||||
// installed.
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertCacheContext('workspace');
|
||||
|
||||
$cache_context = new WorkspaceCacheContext(\Drupal::service('workspaces.manager'));
|
||||
$this->assertSame('live', $cache_context->getContext());
|
||||
|
||||
// Create a node and check that its render array contains the proper cache
|
||||
// context.
|
||||
$this->drupalCreateContentType(['type' => 'page']);
|
||||
$node = $this->createNode();
|
||||
|
||||
// Get a fully built entity view render array.
|
||||
$build = \Drupal::entityTypeManager()->getViewBuilder('node')->view($node, 'full');
|
||||
|
||||
// Render it so the default cache contexts are applied.
|
||||
$renderer->renderRoot($build);
|
||||
$this->assertContains('workspace', $build['#cache']['contexts']);
|
||||
|
||||
$context_tokens = $cache_contexts_manager->convertTokensToKeys($build['#cache']['contexts'])->getKeys();
|
||||
$this->assertContains('[workspace]=live', $context_tokens);
|
||||
|
||||
// Test that a cache entry is created.
|
||||
$cache_bin = $variation_cache_factory->get($build['#cache']['bin']);
|
||||
$this->assertInstanceOf(\stdClass::class, $cache_bin->get($build['#cache']['keys'], CacheableMetadata::createFromRenderArray($build)));
|
||||
|
||||
// Switch to the test workspace and check that the correct workspace cache
|
||||
// context is used.
|
||||
$test_user = $this->drupalCreateUser(['view any workspace']);
|
||||
$this->drupalLogin($test_user);
|
||||
|
||||
$vultures = Workspace::create([
|
||||
'id' => 'vultures',
|
||||
'label' => 'Vultures',
|
||||
]);
|
||||
$vultures->save();
|
||||
|
||||
$workspace_manager = \Drupal::service('workspaces.manager');
|
||||
$workspace_manager->setActiveWorkspace($vultures);
|
||||
|
||||
$cache_context = new WorkspaceCacheContext($workspace_manager);
|
||||
$this->assertSame('vultures', $cache_context->getContext());
|
||||
|
||||
$build = \Drupal::entityTypeManager()->getViewBuilder('node')->view($node, 'full');
|
||||
|
||||
// Render it so the default cache contexts are applied.
|
||||
$renderer->renderRoot($build);
|
||||
$this->assertContains('workspace', $build['#cache']['contexts']);
|
||||
|
||||
$context_tokens = $cache_contexts_manager->convertTokensToKeys($build['#cache']['contexts'])->getKeys();
|
||||
$this->assertContains('[workspace]=vultures', $context_tokens);
|
||||
|
||||
// Test that a cache entry is created.
|
||||
$cache_bin = $variation_cache_factory->get($build['#cache']['bin']);
|
||||
$this->assertInstanceOf(\stdClass::class, $cache_bin->get($build['#cache']['keys'], CacheableMetadata::createFromRenderArray($build)));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests concurrent edits in different workspaces.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceConcurrentEditingTest extends BrowserTestBase {
|
||||
|
||||
use WorkspaceTestUtilities;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block', 'node', 'workspaces', 'workspaces_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests editing a node in multiple workspaces.
|
||||
*/
|
||||
public function testConcurrentEditing(): void {
|
||||
// Create a test node.
|
||||
$this->createContentType(['type' => 'test', 'label' => 'Test']);
|
||||
$this->setupWorkspaceSwitcherBlock();
|
||||
|
||||
$permissions = [
|
||||
'create workspace',
|
||||
'edit own workspace',
|
||||
'view own workspace',
|
||||
'create test content',
|
||||
'edit own test content',
|
||||
];
|
||||
$mayer = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($mayer);
|
||||
|
||||
$test_node = $this->createNodeThroughUi('Test node', 'test');
|
||||
|
||||
// Check that the user can edit the node.
|
||||
$page = $this->getSession()->getPage();
|
||||
$page->hasField('title[0][value]');
|
||||
|
||||
// Create two workspaces.
|
||||
$vultures = $this->createWorkspaceThroughUi('Vultures', 'vultures');
|
||||
$gravity = $this->createWorkspaceThroughUi('Gravity', 'gravity');
|
||||
|
||||
// Edit the node in workspace 'vultures'.
|
||||
$this->switchToWorkspace($vultures);
|
||||
$this->drupalGet('/node/' . $test_node->id() . '/edit');
|
||||
$page = $this->getSession()->getPage();
|
||||
$page->fillField('Title', 'Test node - override');
|
||||
$page->findButton('Save')->click();
|
||||
|
||||
// Check that the user can still edit the node in the same workspace.
|
||||
$this->drupalGet('/node/' . $test_node->id() . '/edit');
|
||||
$page = $this->getSession()->getPage();
|
||||
$this->assertTrue($page->hasField('title[0][value]'));
|
||||
|
||||
// Switch to a different workspace and check that the user can not edit the
|
||||
// node anymore.
|
||||
$this->switchToWorkspace($gravity);
|
||||
$this->drupalGet('/node/' . $test_node->id() . '/edit');
|
||||
$page = $this->getSession()->getPage();
|
||||
$this->assertFalse($page->hasField('title[0][value]'));
|
||||
$page->hasContent('The content is being edited in the Vultures workspace. As a result, your changes cannot be saved.');
|
||||
|
||||
// Check that the node fails validation for API calls.
|
||||
$violations = $test_node->validate();
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertEquals('The content is being edited in the Vultures workspace. As a result, your changes cannot be saved.', $violations->get(0)->getMessage());
|
||||
|
||||
// Switch to the Live version of the site and check that the user still can
|
||||
// not edit the node.
|
||||
$this->switchToLive();
|
||||
$this->drupalGet('/node/' . $test_node->id() . '/edit');
|
||||
$page = $this->getSession()->getPage();
|
||||
$this->assertFalse($page->hasField('title[0][value]'));
|
||||
$page->hasContent('The content is being edited in the Vultures workspace. As a result, your changes cannot be saved.');
|
||||
|
||||
// Check that the node fails validation for API calls.
|
||||
$violations = $test_node->validate();
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertEquals('The content is being edited in the Vultures workspace. As a result, your changes cannot be saved.', $violations->get(0)->getMessage());
|
||||
|
||||
// Publish the changes from the 'Vultures' workspace and check that the node
|
||||
// can be edited again in other workspaces.
|
||||
$vultures->publish();
|
||||
$this->switchToWorkspace($gravity);
|
||||
$this->drupalGet('/node/' . $test_node->id() . '/edit');
|
||||
$page = $this->getSession()->getPage();
|
||||
$this->assertTrue($page->hasField('title[0][value]'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests entity deletions with workspaces.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceEntityDeleteTest extends BrowserTestBase {
|
||||
|
||||
use WorkspaceTestUtilities;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block', 'node', 'user', 'workspaces', 'workspaces_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->createContentType(['type' => 'article', 'label' => 'Article']);
|
||||
$this->setupWorkspaceSwitcherBlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entity deletion with workspaces.
|
||||
*/
|
||||
public function testEntityDelete(): void {
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
$permissions = [
|
||||
'administer workspaces',
|
||||
'create workspace',
|
||||
'access content overview',
|
||||
'administer nodes',
|
||||
'create article content',
|
||||
'edit own article content',
|
||||
'delete own article content',
|
||||
'view own unpublished content',
|
||||
];
|
||||
$editor = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($editor);
|
||||
|
||||
// Create a Dev workspace as a child of Stage.
|
||||
$stage = $this->createWorkspaceThroughUi('Stage', 'stage');
|
||||
$dev = $this->createWorkspaceThroughUi('Dev', 'dev', 'stage');
|
||||
|
||||
// Create a published and an unpublished node in Live.
|
||||
$published_live = $this->createNodeThroughUi('Test 1 published - live', 'article');
|
||||
$unpublished_live = $this->createNodeThroughUi('Test 2 unpublished - live', 'article', FALSE);
|
||||
|
||||
// Create a published and an unpublished node in Stage.
|
||||
$this->switchToWorkspace($stage);
|
||||
$published_stage = $this->createNodeThroughUi('Test 3 published - stage', 'article');
|
||||
$unpublished_stage = $this->createNodeThroughUi('Test 4 unpublished - stage', 'article', FALSE);
|
||||
|
||||
// Check that the Live nodes (both published and unpublished) can not be
|
||||
// deleted, while the Stage nodes can be.
|
||||
$this->drupalGet('admin/content');
|
||||
$assert_session->linkByHrefNotExists($published_live->toUrl('delete-form')->toString());
|
||||
$assert_session->linkByHrefNotExists($unpublished_live->toUrl('delete-form')->toString());
|
||||
$assert_session->linkByHrefExists($published_stage->toUrl('delete-form')->toString());
|
||||
$assert_session->linkByHrefExists($unpublished_stage->toUrl('delete-form')->toString());
|
||||
|
||||
// Switch to Dev and check which nodes can be deleted.
|
||||
$this->switchToWorkspace($dev);
|
||||
$this->drupalGet('admin/content');
|
||||
|
||||
// The two Live nodes have the same deletable status as they had in Stage.
|
||||
$assert_session->linkByHrefNotExists($published_live->toUrl('delete-form')->toString());
|
||||
$assert_session->linkByHrefNotExists($unpublished_live->toUrl('delete-form')->toString());
|
||||
|
||||
// The two Stage nodes should not be deletable in a child workspace (Dev).
|
||||
$assert_session->linkByHrefNotExists($published_stage->toUrl('delete-form')->toString());
|
||||
$assert_session->linkByHrefNotExists($unpublished_stage->toUrl('delete-form')->toString());
|
||||
|
||||
// Add a new revision for each node and check that their 'deletable' status
|
||||
// remains unchanged.
|
||||
$this->switchToWorkspace($stage);
|
||||
$this->drupalGet($published_live->toUrl('edit-form')->toString());
|
||||
$this->submitForm([], 'Save');
|
||||
$this->drupalGet($unpublished_live->toUrl('edit-form')->toString());
|
||||
$this->submitForm([], 'Save');
|
||||
$this->drupalGet($published_stage->toUrl('edit-form')->toString());
|
||||
$this->submitForm([], 'Save');
|
||||
$this->drupalGet($unpublished_stage->toUrl('edit-form')->toString());
|
||||
$this->submitForm([], 'Save');
|
||||
|
||||
$this->drupalGet('admin/content');
|
||||
$assert_session->linkByHrefNotExists($published_live->toUrl('delete-form')->toString());
|
||||
$assert_session->linkByHrefNotExists($unpublished_live->toUrl('delete-form')->toString());
|
||||
$assert_session->linkByHrefExists($published_stage->toUrl('delete-form')->toString());
|
||||
$assert_session->linkByHrefExists($unpublished_stage->toUrl('delete-form')->toString());
|
||||
|
||||
// Publish the Stage workspace and check that no entity can be deleted
|
||||
// anymore in Stage nor Dev.
|
||||
$stage->publish();
|
||||
$this->drupalGet('admin/content');
|
||||
$assert_session->linkByHrefNotExists($published_live->toUrl('delete-form')->toString());
|
||||
$assert_session->linkByHrefNotExists($unpublished_live->toUrl('delete-form')->toString());
|
||||
$assert_session->linkByHrefNotExists($published_stage->toUrl('delete-form')->toString());
|
||||
$assert_session->linkByHrefNotExists($unpublished_stage->toUrl('delete-form')->toString());
|
||||
|
||||
$this->switchToWorkspace($dev);
|
||||
$this->drupalGet('admin/content');
|
||||
$assert_session->linkByHrefNotExists($published_live->toUrl('delete-form')->toString());
|
||||
$assert_session->linkByHrefNotExists($unpublished_live->toUrl('delete-form')->toString());
|
||||
$assert_session->linkByHrefNotExists($published_stage->toUrl('delete-form')->toString());
|
||||
$assert_session->linkByHrefNotExists($unpublished_stage->toUrl('delete-form')->toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test node deletion with workspaces and the 'bypass node access' permission.
|
||||
*/
|
||||
public function testNodeDeleteWithBypassAccessPermission(): void {
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
$permissions = [
|
||||
'administer workspaces',
|
||||
'create workspace',
|
||||
'access content overview',
|
||||
'bypass node access',
|
||||
];
|
||||
$editor = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($editor);
|
||||
|
||||
// Create a published node in Live.
|
||||
$published_live = $this->createNodeThroughUi('Test 1 published - live', 'article');
|
||||
|
||||
$this->createAndActivateWorkspaceThroughUi('Stage', 'stage');
|
||||
|
||||
// A user with the 'bypass node access' permission will be able to see the
|
||||
// 'Delete' operation button, but it shouldn't be able to perform the
|
||||
// deletion.
|
||||
$this->drupalGet('admin/content');
|
||||
$assert_session->linkByHrefExists($published_live->toUrl('delete-form')->toString());
|
||||
$this->clickLink('Delete');
|
||||
$assert_session->pageTextContains('This content item can only be deleted in the Live workspace.');
|
||||
$assert_session->buttonNotExists('Delete');
|
||||
|
||||
$this->drupalGet($published_live->toUrl('delete-form')->toString());
|
||||
$assert_session->pageTextContains('This content item can only be deleted in the Live workspace.');
|
||||
$assert_session->buttonNotExists('Delete');
|
||||
|
||||
// Go back to Live and check that the delete form is not affected by the
|
||||
// workspace delete protection.
|
||||
$this->switchToLive();
|
||||
$this->drupalGet($published_live->toUrl('delete-form')->toString());
|
||||
$assert_session->pageTextNotContains('This content item can only be deleted in the Live workspace.');
|
||||
$assert_session->buttonExists('Delete');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests Workspaces form validation.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceFormValidationTest extends BrowserTestBase {
|
||||
|
||||
use WorkspaceTestUtilities;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block', 'form_test', 'workspaces'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser(['administer workspaces']));
|
||||
$this->setupWorkspaceSwitcherBlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests partial form validation through #limit_validation_errors.
|
||||
*/
|
||||
public function testValidateLimitErrors(): void {
|
||||
$this->createAndActivateWorkspaceThroughUi();
|
||||
|
||||
$edit = [
|
||||
'test' => 'test1',
|
||||
'test_numeric_index[0]' => 'test2',
|
||||
'test_substring[foo]' => 'test3',
|
||||
];
|
||||
$path = 'form-test/limit-validation-errors';
|
||||
|
||||
// Submit the form by pressing all the 'Partial validate' buttons.
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Partial validate');
|
||||
$this->assertSession()->pageTextContains('This form can only be submitted in the default workspace.');
|
||||
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Partial validate (numeric index)');
|
||||
$this->assertSession()->pageTextContains('This form can only be submitted in the default workspace.');
|
||||
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Partial validate (substring)');
|
||||
$this->assertSession()->pageTextContains('This form can only be submitted in the default workspace.');
|
||||
|
||||
// Now test full form validation.
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Full validate');
|
||||
$this->assertSession()->pageTextContains('This form can only be submitted in the default workspace.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests workspace integration for custom menu links.
|
||||
*
|
||||
* @group workspaces
|
||||
* @group menu_link_content
|
||||
*/
|
||||
class WorkspaceMenuLinkContentIntegrationTest extends BrowserTestBase {
|
||||
|
||||
use WorkspaceTestUtilities;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'block',
|
||||
'menu_link_content',
|
||||
'menu_ui',
|
||||
'node',
|
||||
'workspaces',
|
||||
'workspaces_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$permissions = [
|
||||
'access administration pages',
|
||||
'administer menu',
|
||||
'administer site configuration',
|
||||
'administer workspaces',
|
||||
];
|
||||
$this->drupalLogin($this->drupalCreateUser($permissions));
|
||||
$this->drupalPlaceBlock('system_menu_block:main');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests custom menu links in non-default workspaces.
|
||||
*/
|
||||
public function testWorkspacesWithCustomMenuLinks(): void {
|
||||
$stage = $this->createWorkspaceThroughUi('Stage', 'stage');
|
||||
|
||||
$this->setupWorkspaceSwitcherBlock();
|
||||
|
||||
$default_title = 'default';
|
||||
$default_link = '#live';
|
||||
|
||||
// Add a new menu link in Live.
|
||||
$this->drupalGet('admin/structure/menu/manage/main/add');
|
||||
$this->submitForm([
|
||||
'title[0][value]' => $default_title,
|
||||
'link[0][uri]' => $default_link,
|
||||
], 'Save');
|
||||
$menu_links = \Drupal::entityTypeManager()
|
||||
->getStorage('menu_link_content')
|
||||
->loadByProperties(['title' => $default_title]);
|
||||
$menu_link = reset($menu_links);
|
||||
|
||||
$pending_title = 'pending';
|
||||
$pending_link = 'http://example.com';
|
||||
|
||||
// Change the menu link in 'stage' and check that the updated values are
|
||||
// visible in that workspace.
|
||||
$this->switchToWorkspace($stage);
|
||||
$this->drupalGet("admin/structure/menu/item/{$menu_link->id()}/edit");
|
||||
$this->submitForm([
|
||||
'title[0][value]' => $pending_title,
|
||||
'link[0][uri]' => $pending_link,
|
||||
], 'Save');
|
||||
|
||||
$this->drupalGet('');
|
||||
$assert_session = $this->assertSession();
|
||||
$assert_session->linkExists($pending_title);
|
||||
$assert_session->linkByHrefExists($pending_link);
|
||||
|
||||
// Add a new menu link in the Stage workspace.
|
||||
$this->drupalGet('admin/structure/menu/manage/main/add');
|
||||
$this->submitForm([
|
||||
'title[0][value]' => 'stage link',
|
||||
'link[0][uri]' => '#stage',
|
||||
], 'Save');
|
||||
|
||||
$this->drupalGet('');
|
||||
$assert_session->linkExists('stage link');
|
||||
$assert_session->linkByHrefExists('#stage');
|
||||
|
||||
// Switch back to the Live workspace and check that the menu link has the
|
||||
// default values.
|
||||
$this->switchToLive();
|
||||
$this->drupalGet('');
|
||||
$assert_session->linkExists($default_title);
|
||||
$assert_session->linkByHrefExists($default_link);
|
||||
$assert_session->linkNotExists($pending_title);
|
||||
$assert_session->linkByHrefNotExists($pending_link);
|
||||
$assert_session->linkNotExists('stage link');
|
||||
$assert_session->linkByHrefNotExists('#stage');
|
||||
|
||||
// Publish the workspace and check that the menu link has been updated.
|
||||
$stage->publish();
|
||||
$this->drupalGet('');
|
||||
$assert_session->linkNotExists($default_title);
|
||||
$assert_session->linkByHrefNotExists($default_link);
|
||||
$assert_session->linkExists($pending_title);
|
||||
$assert_session->linkByHrefExists($pending_link);
|
||||
$assert_session->linkExists('stage link');
|
||||
$assert_session->linkByHrefExists('#stage');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Tests permission controls on workspaces.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspacePermissionsTest extends BrowserTestBase {
|
||||
|
||||
use WorkspaceTestUtilities;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['workspaces', 'workspaces_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Verifies that a user can create but not edit a workspace.
|
||||
*/
|
||||
public function testCreateWorkspace(): void {
|
||||
$editor = $this->drupalCreateUser([
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
'create workspace',
|
||||
]);
|
||||
|
||||
// Login as a limited-access user and create a workspace.
|
||||
$this->drupalLogin($editor);
|
||||
$this->createWorkspaceThroughUi('Bears', 'bears');
|
||||
|
||||
// Now edit that same workspace; We shouldn't be able to do so, since
|
||||
// we don't have edit permissions.
|
||||
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $etm */
|
||||
$etm = \Drupal::service('entity_type.manager');
|
||||
/** @var \Drupal\workspaces\WorkspaceInterface $bears */
|
||||
$entity_list = $etm->getStorage('workspace')->loadByProperties(['label' => 'Bears']);
|
||||
$bears = current($entity_list);
|
||||
|
||||
$this->drupalGet("/admin/config/workflow/workspaces/manage/{$bears->id()}/edit");
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a user can create and edit only their own workspace.
|
||||
*/
|
||||
public function testEditOwnWorkspace(): void {
|
||||
$permissions = [
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
'create workspace',
|
||||
'edit own workspace',
|
||||
];
|
||||
|
||||
$editor1 = $this->drupalCreateUser($permissions);
|
||||
|
||||
// Login as a limited-access user and create a workspace.
|
||||
$this->drupalLogin($editor1);
|
||||
$this->createWorkspaceThroughUi('Bears', 'bears');
|
||||
|
||||
// Now edit that same workspace; We should be able to do so.
|
||||
$bears = Workspace::load('bears');
|
||||
|
||||
$this->drupalGet("/admin/config/workflow/workspaces/manage/{$bears->id()}/edit");
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$page->fillField('label', 'Bears again');
|
||||
$page->fillField('id', 'bears');
|
||||
$page->findButton('Save')->click();
|
||||
$page->hasContent('Bears again (bears)');
|
||||
|
||||
// Now login as a different user and ensure they don't have edit access,
|
||||
// and vice versa.
|
||||
$editor2 = $this->drupalCreateUser($permissions);
|
||||
|
||||
$this->drupalLogin($editor2);
|
||||
$this->createWorkspaceThroughUi('Packers', 'packers');
|
||||
$packers = Workspace::load('packers');
|
||||
|
||||
$this->drupalGet("/admin/config/workflow/workspaces/manage/{$packers->id()}/edit");
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$this->drupalGet("/admin/config/workflow/workspaces/manage/{$bears->id()}/edit");
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a user can edit any workspace.
|
||||
*/
|
||||
public function testEditAnyWorkspace(): void {
|
||||
$permissions = [
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
'create workspace',
|
||||
'edit own workspace',
|
||||
];
|
||||
|
||||
$editor1 = $this->drupalCreateUser($permissions);
|
||||
|
||||
// Login as a limited-access user and create a workspace.
|
||||
$this->drupalLogin($editor1);
|
||||
$this->createWorkspaceThroughUi('Bears', 'bears');
|
||||
|
||||
// Now edit that same workspace; We should be able to do so.
|
||||
$bears = Workspace::load('bears');
|
||||
|
||||
$this->drupalGet("/admin/config/workflow/workspaces/manage/{$bears->id()}/edit");
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$page->fillField('label', 'Bears again');
|
||||
$page->fillField('id', 'bears');
|
||||
$page->findButton('Save')->click();
|
||||
$page->hasContent('Bears again (bears)');
|
||||
|
||||
// Now login as a different user and ensure they don't have edit access,
|
||||
// and vice versa.
|
||||
$admin = $this->drupalCreateUser(array_merge($permissions, ['edit any workspace']));
|
||||
|
||||
$this->drupalLogin($admin);
|
||||
$this->createWorkspaceThroughUi('Packers', 'packers');
|
||||
$packers = Workspace::load('packers');
|
||||
|
||||
$this->drupalGet("/admin/config/workflow/workspaces/manage/{$packers->id()}/edit");
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$this->drupalGet("/admin/config/workflow/workspaces/manage/{$bears->id()}/edit");
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a user can create and delete only their own workspace.
|
||||
*/
|
||||
public function testDeleteOwnWorkspace(): void {
|
||||
$permissions = [
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
'create workspace',
|
||||
'delete own workspace',
|
||||
];
|
||||
$editor1 = $this->drupalCreateUser($permissions);
|
||||
|
||||
// Login as a limited-access user and create a workspace.
|
||||
$this->drupalLogin($editor1);
|
||||
$bears = $this->createWorkspaceThroughUi('Bears', 'bears');
|
||||
|
||||
// Now try to delete that same workspace; We should be able to do so.
|
||||
$this->drupalGet("/admin/config/workflow/workspaces/manage/{$bears->id()}/delete");
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Now login as a different user and ensure they don't have edit access,
|
||||
// and vice versa.
|
||||
$editor2 = $this->drupalCreateUser($permissions);
|
||||
|
||||
$this->drupalLogin($editor2);
|
||||
$packers = $this->createWorkspaceThroughUi('Packers', 'packers');
|
||||
|
||||
$this->drupalGet("/admin/config/workflow/workspaces/manage/{$packers->id()}/delete");
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$this->drupalGet("/admin/config/workflow/workspaces/manage/{$bears->id()}/delete");
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a user can delete any workspace.
|
||||
*/
|
||||
public function testDeleteAnyWorkspace(): void {
|
||||
$permissions = [
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
'create workspace',
|
||||
'delete own workspace',
|
||||
];
|
||||
$editor1 = $this->drupalCreateUser($permissions);
|
||||
|
||||
// Login as a limited-access user and create a workspace.
|
||||
$this->drupalLogin($editor1);
|
||||
$bears = $this->createWorkspaceThroughUi('Bears', 'bears');
|
||||
|
||||
// Now edit that same workspace; We should be able to do so.
|
||||
$this->drupalGet("/admin/config/workflow/workspaces/manage/{$bears->id()}/delete");
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Now login as a different user and ensure they have delete access on both
|
||||
// workspaces.
|
||||
$admin = $this->drupalCreateUser(array_merge($permissions, ['delete any workspace']));
|
||||
|
||||
$this->drupalLogin($admin);
|
||||
$packers = $this->createWorkspaceThroughUi('Packers', 'packers');
|
||||
|
||||
$this->drupalGet("/admin/config/workflow/workspaces/manage/{$packers->id()}/delete");
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$this->drupalGet("/admin/config/workflow/workspaces/manage/{$bears->id()}/delete");
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional;
|
||||
|
||||
use Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Tests workspace switching functionality.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceSwitcherTest extends BrowserTestBase {
|
||||
|
||||
use AssertPageCacheContextsAndTagsTrait;
|
||||
use WorkspaceTestUtilities;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'block',
|
||||
'dynamic_page_cache',
|
||||
'node',
|
||||
'toolbar',
|
||||
'workspaces',
|
||||
'workspaces_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$permissions = [
|
||||
'create workspace',
|
||||
'edit own workspace',
|
||||
'view own workspace',
|
||||
'bypass entity access own workspace',
|
||||
];
|
||||
|
||||
$this->setupWorkspaceSwitcherBlock();
|
||||
|
||||
$mayer = $this->drupalCreateUser($permissions);
|
||||
$this->drupalLogin($mayer);
|
||||
|
||||
$this->createWorkspaceThroughUi('Vultures', 'vultures');
|
||||
$this->createWorkspaceThroughUi('Gravity', 'gravity');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests switching workspace via the switcher block and admin page.
|
||||
*/
|
||||
public function testSwitchingWorkspaces(): void {
|
||||
$vultures = Workspace::load('vultures');
|
||||
$gravity = Workspace::load('gravity');
|
||||
$this->switchToWorkspace($vultures);
|
||||
|
||||
// Confirm the block shows on the front page.
|
||||
$this->drupalGet('<front>');
|
||||
$page = $this->getSession()->getPage();
|
||||
$this->assertTrue($page->hasContent('Workspace switcher'));
|
||||
|
||||
$this->drupalGet('/admin/config/workflow/workspaces/manage/' . $gravity->id() . '/activate');
|
||||
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$page = $this->getSession()->getPage();
|
||||
$page->findButton('Confirm')->click();
|
||||
|
||||
// Check that WorkspaceCacheContext provides the cache context used to
|
||||
// support its functionality.
|
||||
$this->assertCacheContext('session');
|
||||
|
||||
$page->findLink($gravity->label());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests switching workspace via a query parameter.
|
||||
*/
|
||||
public function testQueryParameterNegotiator(): void {
|
||||
$web_assert = $this->assertSession();
|
||||
// Initially the default workspace should be active.
|
||||
$web_assert->elementContains('css', '#block-workspace-switcher', 'None');
|
||||
|
||||
// When adding a query parameter the workspace will be switched.
|
||||
$current_user_url = \Drupal::currentUser()->getAccount()->toUrl();
|
||||
$this->drupalGet($current_user_url, ['query' => ['workspace' => 'vultures']]);
|
||||
$web_assert->elementContains('css', '#block-workspace-switcher', 'Vultures');
|
||||
|
||||
// The workspace switching via query parameter should persist.
|
||||
$this->drupalGet($current_user_url);
|
||||
$web_assert->elementContains('css', '#block-workspace-switcher', 'Vultures');
|
||||
|
||||
// Check that WorkspaceCacheContext provides the cache context used to
|
||||
// support its functionality.
|
||||
$this->assertCacheContext('session');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the toolbar workspace switcher doesn't disable the page cache.
|
||||
*/
|
||||
public function testToolbarSwitcherDynamicPageCache(): void {
|
||||
$node_type = $this->drupalCreateContentType();
|
||||
$node = $this->drupalCreateNode(['type' => $node_type->id()]);
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access toolbar',
|
||||
'view any workspace',
|
||||
]));
|
||||
$this->drupalGet($node->toUrl());
|
||||
$this->assertSession()->responseHeaderEquals(DynamicPageCacheSubscriber::HEADER, 'MISS');
|
||||
// Reload the page, it should be cached now.
|
||||
$this->drupalGet($node->toUrl());
|
||||
$this->assertSession()->elementExists('css', '.workspaces-toolbar-tab');
|
||||
$this->assertSession()->responseHeaderEquals(DynamicPageCacheSubscriber::HEADER, 'HIT');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,381 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
|
||||
|
||||
/**
|
||||
* Test the workspace entity.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceTest extends BrowserTestBase {
|
||||
|
||||
use WorkspaceTestUtilities;
|
||||
use ContentTypeCreationTrait;
|
||||
use TaxonomyTestTrait;
|
||||
use FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'block',
|
||||
'field_ui',
|
||||
'node',
|
||||
'taxonomy',
|
||||
'toolbar',
|
||||
'user',
|
||||
'workspaces',
|
||||
'workspaces_ui',
|
||||
'workspaces_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A test user.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User
|
||||
*/
|
||||
protected $editor1;
|
||||
|
||||
/**
|
||||
* A test user.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User
|
||||
*/
|
||||
protected $editor2;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$permissions = [
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
'create workspace',
|
||||
'edit own workspace',
|
||||
'edit any workspace',
|
||||
'view any workspace',
|
||||
'view own workspace',
|
||||
'access toolbar',
|
||||
];
|
||||
|
||||
$this->editor1 = $this->drupalCreateUser($permissions);
|
||||
$this->editor2 = $this->drupalCreateUser($permissions);
|
||||
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creating a workspace with special characters.
|
||||
*/
|
||||
public function testSpecialCharacters(): void {
|
||||
$this->drupalLogin($this->editor1);
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
// Test a valid workspace name.
|
||||
$this->createAndActivateWorkspaceThroughUi('Workspace 1', 'workspace_1');
|
||||
$this->assertSession()->elementTextContains('css', '.workspaces-toolbar-tab', 'Workspace 1');
|
||||
|
||||
// Test and invalid workspace name.
|
||||
$this->drupalGet('/admin/config/workflow/workspaces/add');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$page->fillField('label', 'workspace2');
|
||||
$page->fillField('id', 'A!"£%^&*{}#~@?');
|
||||
$page->findButton('Save')->click();
|
||||
$page->hasContent("This value is not valid");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the toolbar correctly shows the active workspace.
|
||||
*/
|
||||
public function testWorkspaceToolbar(): void {
|
||||
$this->drupalLogin($this->editor1);
|
||||
|
||||
$this->drupalGet('/admin/config/workflow/workspaces/add');
|
||||
$this->submitForm([
|
||||
'id' => 'test_workspace',
|
||||
'label' => 'Test workspace',
|
||||
], 'Save');
|
||||
|
||||
// Activate the test workspace.
|
||||
$this->drupalGet('/admin/config/workflow/workspaces/manage/test_workspace/activate');
|
||||
$this->submitForm([], 'Confirm');
|
||||
|
||||
$this->drupalGet('<front>');
|
||||
$page = $this->getSession()->getPage();
|
||||
// Toolbar should show the correct label.
|
||||
$this->assertTrue($page->hasLink('Test workspace'));
|
||||
|
||||
// Change the workspace label.
|
||||
$this->drupalGet('/admin/config/workflow/workspaces/manage/test_workspace/edit');
|
||||
$this->submitForm(['label' => 'New name'], 'Save');
|
||||
|
||||
$this->drupalGet('<front>');
|
||||
$page = $this->getSession()->getPage();
|
||||
// Toolbar should show the new label.
|
||||
$this->assertTrue($page->hasLink('New name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests changing the owner of a workspace.
|
||||
*/
|
||||
public function testWorkspaceOwner(): void {
|
||||
$this->drupalLogin($this->editor1);
|
||||
|
||||
$this->drupalGet('/admin/config/workflow/workspaces/add');
|
||||
$this->submitForm([
|
||||
'id' => 'test_workspace',
|
||||
'label' => 'Test workspace',
|
||||
], 'Save');
|
||||
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('workspace');
|
||||
$test_workspace = $storage->load('test_workspace');
|
||||
$this->assertEquals($this->editor1->id(), $test_workspace->getOwnerId());
|
||||
|
||||
$this->drupalGet('/admin/config/workflow/workspaces/manage/test_workspace/edit');
|
||||
$this->submitForm(['uid[0][target_id]' => $this->editor2->getAccountName()], 'Save');
|
||||
|
||||
$test_workspace = $storage->loadUnchanged('test_workspace');
|
||||
$this->assertEquals($this->editor2->id(), $test_workspace->getOwnerId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that editing a workspace creates a new revision.
|
||||
*/
|
||||
public function testWorkspaceFormRevisions(): void {
|
||||
$this->drupalLogin($this->editor1);
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('workspace');
|
||||
$this->createWorkspaceThroughUi('Stage', 'stage');
|
||||
|
||||
// The current 'stage' workspace entity should be revision 1.
|
||||
$stage_workspace = $storage->load('stage');
|
||||
$this->assertEquals('1', $stage_workspace->getRevisionId());
|
||||
|
||||
// Re-save the 'stage' workspace via the UI to create revision 2.
|
||||
$this->drupalGet($stage_workspace->toUrl('edit-form')->toString());
|
||||
$this->submitForm([], 'Save');
|
||||
$stage_workspace = $storage->loadUnchanged('stage');
|
||||
$this->assertEquals('2', $stage_workspace->getRevisionId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the manage workspace page.
|
||||
*/
|
||||
public function testWorkspaceManagePage(): void {
|
||||
$this->drupalCreateContentType(['type' => 'test', 'label' => 'Test']);
|
||||
|
||||
$permissions = [
|
||||
'administer taxonomy',
|
||||
'administer workspaces',
|
||||
'create test content',
|
||||
'delete any test content',
|
||||
];
|
||||
$this->drupalLogin($this->drupalCreateUser($permissions));
|
||||
$this->setupWorkspaceSwitcherBlock();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
$vocabulary = $this->createVocabulary();
|
||||
|
||||
$test_1 = $this->createWorkspaceThroughUi('Test 1', 'test_1');
|
||||
$test_2 = $this->createWorkspaceThroughUi('Test 2', 'test_2');
|
||||
|
||||
$this->switchToWorkspace($test_1);
|
||||
|
||||
// Check that the 'test_1' workspace doesn't contain any changes initially.
|
||||
$this->drupalGet($test_1->toUrl()->toString());
|
||||
$assert_session->pageTextContains('This workspace has no changes.');
|
||||
|
||||
// Check that the 'Switch to this workspace' action link is not displayed on
|
||||
// the manage page of the currently active workspace.
|
||||
$assert_session->linkNotExists('Switch to this workspace');
|
||||
$this->drupalGet($test_2->toUrl()->toString());
|
||||
$assert_session->linkExists('Switch to this workspace');
|
||||
|
||||
// Create some test content.
|
||||
$this->createNodeThroughUi('Node 1', 'test');
|
||||
$this->createNodeThroughUi('Node 2', 'test');
|
||||
$edit = [
|
||||
'name[0][value]' => 'Term 1',
|
||||
];
|
||||
$this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary->id() . '/add');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
$this->drupalGet($test_1->toUrl()->toString());
|
||||
$assert_session->pageTextContains('2 content items, 1 taxonomy term');
|
||||
$assert_session->linkExists('Node 1');
|
||||
$assert_session->linkExists('Node 2');
|
||||
$assert_session->linkExists('Term 1');
|
||||
|
||||
// Create 50 more nodes to test the pagination.
|
||||
for ($i = 3; $i < 53; $i++) {
|
||||
$this->createNodeThroughUi('Node ' . $i, 'test');
|
||||
}
|
||||
|
||||
$this->drupalGet($test_1->toUrl()->toString());
|
||||
$assert_session->pageTextContains('52 content items');
|
||||
$assert_session->pageTextContains('1 taxonomy term');
|
||||
$assert_session->linkExists('Node 52');
|
||||
$assert_session->linkExists('Node 3');
|
||||
$assert_session->linkNotExists('Term 1');
|
||||
|
||||
$this->drupalGet($test_1->toUrl()->toString(), ['query' => ['page' => '1']]);
|
||||
$assert_session->linkExists('Node 1');
|
||||
$assert_session->linkExists('Node 2');
|
||||
$assert_session->linkExists('Term 1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding new fields to workspace entities.
|
||||
*/
|
||||
public function testWorkspaceFieldUi(): void {
|
||||
$user = $this->drupalCreateUser([
|
||||
'administer workspaces',
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
'administer workspace fields',
|
||||
'administer workspace display',
|
||||
'administer workspace form display',
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
$this->drupalGet('admin/config/workflow/workspaces/fields');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Create a new filed.
|
||||
$field_name = $this->randomMachineName();
|
||||
$field_label = $this->randomMachineName();
|
||||
$this->fieldUIAddNewField('admin/config/workflow/workspaces', $field_name, $field_label, 'string');
|
||||
|
||||
// Check that the field is displayed on the manage form display page.
|
||||
$this->drupalGet('admin/config/workflow/workspaces/form-display');
|
||||
$this->assertSession()->pageTextContains($field_label);
|
||||
|
||||
// Check that the field is displayed on the manage display page.
|
||||
$this->drupalGet('admin/config/workflow/workspaces/display');
|
||||
$this->assertSession()->pageTextContains($field_label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a workspace with existing content may be deleted.
|
||||
*/
|
||||
public function testDeleteWorkspaceWithExistingContent(): void {
|
||||
$this->createContentType(['type' => 'test', 'label' => 'Test']);
|
||||
|
||||
// Login and create a workspace.
|
||||
$permissions = [
|
||||
'administer workspaces',
|
||||
'create test content',
|
||||
'delete any test content',
|
||||
];
|
||||
$this->drupalLogin($this->drupalCreateUser($permissions));
|
||||
$this->createAndActivateWorkspaceThroughUi('May 4', 'may_4');
|
||||
|
||||
// Create a node in the workspace.
|
||||
$this->createNodeThroughUi('A mayfly flies / In May or June', 'test');
|
||||
|
||||
// Delete the workspace.
|
||||
$this->drupalGet('/admin/config/workflow/workspaces/manage/may_4/delete');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$page = $this->getSession()->getPage();
|
||||
$page->findButton('Delete')->click();
|
||||
$page->hasContent('The workspace May 4 has been deleted.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Workspaces listing UI.
|
||||
*/
|
||||
public function testWorkspaceList(): void {
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
// Login and create a workspace.
|
||||
$this->drupalLogin($this->editor1);
|
||||
$this->createWorkspaceThroughUi('Summer event', 'summer_event');
|
||||
|
||||
// Check that Live is the current active workspace.
|
||||
$this->drupalGet('/admin/config/workflow/workspaces');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$active_workspace_row = $page->find('css', '.active-workspace');
|
||||
$this->assertTrue($active_workspace_row->hasClass('active-workspace--default'));
|
||||
$this->assertEquals('Live', $active_workspace_row->find('css', 'td:first-of-type')->getText());
|
||||
|
||||
// The 'Switch to Live' operation is not shown when 'Live' is the active
|
||||
// workspace.
|
||||
$assert_session->linkNotExists('Switch to Live');
|
||||
|
||||
// Switch to another workspace and check that it has been marked as active.
|
||||
$page->clickLink('Switch to Summer event');
|
||||
$page->pressButton('Confirm');
|
||||
|
||||
$active_workspace_row = $page->find('css', '.active-workspace');
|
||||
$this->assertTrue($active_workspace_row->hasClass('active-workspace--not-default'));
|
||||
$this->assertEquals('Summer event', $active_workspace_row->find('css', 'td:first-of-type')->getText());
|
||||
|
||||
// 'Live' is no longer the active workspace, so it's 'Switch to Live'
|
||||
// operation should be visible now.
|
||||
$assert_session->linkExists('Switch to Live');
|
||||
|
||||
// Delete any of the workspace owners and visit workspaces listing.
|
||||
$this->drupalLogin($this->editor2);
|
||||
user_cancel([], $this->editor1->id(), 'user_cancel_reassign');
|
||||
$user = \Drupal::service('entity_type.manager')->getStorage('user')->load($this->editor1->id());
|
||||
$user->delete();
|
||||
$this->drupalGet('/admin/config/workflow/workspaces');
|
||||
$this->assertSession()->pageTextContains('Summer event');
|
||||
$summer_event_workspace_row = $page->find('css', 'table tbody tr:nth-of-type(2)');
|
||||
$this->assertEquals('N/A', $summer_event_workspace_row->find('css', 'td:nth-of-type(2)')->getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a workspace can be published.
|
||||
*/
|
||||
public function testPublishWorkspace(): void {
|
||||
$this->createContentType(['type' => 'test', 'label' => 'Test']);
|
||||
$permissions = [
|
||||
'administer workspaces',
|
||||
'create test content',
|
||||
];
|
||||
$this->drupalLogin($this->drupalCreateUser($permissions));
|
||||
|
||||
$this->drupalGet('/admin/config/workflow/workspaces/add');
|
||||
$this->submitForm([
|
||||
'id' => 'test_workspace',
|
||||
'label' => 'Test workspace',
|
||||
], 'Save');
|
||||
|
||||
// Activate the test workspace.
|
||||
$this->drupalGet('/admin/config/workflow/workspaces/manage/test_workspace/activate');
|
||||
$this->submitForm([], 'Confirm');
|
||||
|
||||
$this->drupalGet('/admin/config/workflow/workspaces/manage/test_workspace/publish');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('There are no changes that can be published from Test workspace to Live.');
|
||||
|
||||
// Create a node in the workspace.
|
||||
$this->drupalGet('/node/add/test');
|
||||
$this->assertEquals(1, \Drupal::keyValue('ws_test')->get('node.hook_entity_create.count'));
|
||||
$this->submitForm(['title[0][value]' => 'Test node'], 'Save');
|
||||
|
||||
$this->drupalGet('/admin/config/workflow/workspaces/manage/test_workspace/publish');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('There is 1 item that can be published from Test workspace to Live');
|
||||
|
||||
$this->getSession()->getPage()->pressButton('Publish 1 item to Live');
|
||||
$this->assertSession()->pageTextContains('Successful publication.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Tests\block\Traits\BlockCreationTrait;
|
||||
use Drupal\workspaces\Entity\Handler\IgnoredWorkspaceHandler;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
use Drupal\workspaces\WorkspaceInterface;
|
||||
|
||||
/**
|
||||
* Utility methods for use in BrowserTestBase tests.
|
||||
*
|
||||
* This trait will not work if not used in a child of BrowserTestBase.
|
||||
*/
|
||||
trait WorkspaceTestUtilities {
|
||||
|
||||
use BlockCreationTrait;
|
||||
|
||||
/**
|
||||
* Signifies that the switcher block is configured.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $switcherBlockConfigured = FALSE;
|
||||
|
||||
/**
|
||||
* Loads a single entity by its label.
|
||||
*
|
||||
* The UI approach to creating an entity doesn't make it easy to know what
|
||||
* the ID is, so this lets us make paths for an entity after it's created.
|
||||
*
|
||||
* @param string $type
|
||||
* The type of entity to load.
|
||||
* @param string $label
|
||||
* The label of the entity to load.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* The entity.
|
||||
*/
|
||||
protected function getOneEntityByLabel($type, $label): EntityInterface {
|
||||
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
|
||||
$entity_type_manager = \Drupal::service('entity_type.manager');
|
||||
$property = $entity_type_manager->getDefinition($type)->getKey('label');
|
||||
$entity_list = $entity_type_manager->getStorage($type)->loadByProperties([$property => $label]);
|
||||
$entity = current($entity_list);
|
||||
if (!$entity) {
|
||||
$this->fail("No {$type} entity named {$label} found.");
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and activates a new Workspace through the UI.
|
||||
*
|
||||
* @param string|null $label
|
||||
* The label of the workspace to create.
|
||||
* @param string|null $id
|
||||
* The ID of the workspace to create.
|
||||
* @param string $parent
|
||||
* (optional) The ID of the parent workspace. Defaults to '_none'.
|
||||
*
|
||||
* @return \Drupal\workspaces\WorkspaceInterface
|
||||
* The workspace that was just created.
|
||||
*/
|
||||
protected function createAndActivateWorkspaceThroughUi(?string $label = NULL, ?string $id = NULL, string $parent = '_none'): WorkspaceInterface {
|
||||
$id ??= $this->randomMachineName();
|
||||
$label ??= $this->randomString();
|
||||
|
||||
$this->drupalGet('/admin/config/workflow/workspaces/add');
|
||||
$this->submitForm([
|
||||
'id' => $id,
|
||||
'label' => $label,
|
||||
'parent' => $parent,
|
||||
], 'Save and switch');
|
||||
|
||||
$this->getSession()->getPage()->hasContent("$label ($id)");
|
||||
|
||||
// Keep the test runner in sync with the system under test.
|
||||
$workspace = Workspace::load($id);
|
||||
\Drupal::service('workspaces.manager')->setActiveWorkspace($workspace);
|
||||
|
||||
return $workspace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Workspace through the UI.
|
||||
*
|
||||
* @param string|null $label
|
||||
* The label of the workspace to create.
|
||||
* @param string|null $id
|
||||
* The ID of the workspace to create.
|
||||
* @param string $parent
|
||||
* (optional) The ID of the parent workspace. Defaults to '_none'.
|
||||
*
|
||||
* @return \Drupal\workspaces\WorkspaceInterface
|
||||
* The workspace that was just created.
|
||||
*/
|
||||
protected function createWorkspaceThroughUi(?string $label = NULL, ?string $id = NULL, string $parent = '_none') {
|
||||
$id ??= $this->randomMachineName();
|
||||
$label ??= $this->randomString();
|
||||
|
||||
$this->drupalGet('/admin/config/workflow/workspaces/add');
|
||||
$this->submitForm([
|
||||
'id' => $id,
|
||||
'label' => $label,
|
||||
'parent' => $parent,
|
||||
], 'Save');
|
||||
|
||||
$this->getSession()->getPage()->hasContent("$label ($id)");
|
||||
|
||||
return Workspace::load($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the workspace switcher block to the site.
|
||||
*
|
||||
* This is necessary for switchToWorkspace() to function correctly.
|
||||
*/
|
||||
protected function setupWorkspaceSwitcherBlock() {
|
||||
// Add the block to the sidebar.
|
||||
$this->placeBlock('workspace_switcher', [
|
||||
'id' => 'workspace_switcher',
|
||||
'region' => 'sidebar_first',
|
||||
'label' => 'Workspace switcher',
|
||||
]);
|
||||
$this->drupalGet('<front>');
|
||||
|
||||
$this->switcherBlockConfigured = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a given workspace as "active" for subsequent requests.
|
||||
*
|
||||
* This assumes that the switcher block has already been setup by calling
|
||||
* setupWorkspaceSwitcherBlock().
|
||||
*
|
||||
* @param \Drupal\workspaces\WorkspaceInterface $workspace
|
||||
* The workspace to set active.
|
||||
*/
|
||||
protected function switchToWorkspace(WorkspaceInterface $workspace) {
|
||||
$this->assertTrue($this->switcherBlockConfigured, 'This test was not written correctly: you must call setupWorkspaceSwitcherBlock() before switchToWorkspace()');
|
||||
/** @var \Drupal\Tests\WebAssert $session */
|
||||
$session = $this->assertSession();
|
||||
$session->buttonExists('Activate');
|
||||
$this->submitForm(['workspace_id' => $workspace->id()], 'Activate');
|
||||
$session->pageTextContains($workspace->label() . ' is now the active workspace.');
|
||||
// Keep the test runner in sync with the system under test.
|
||||
\Drupal::service('workspaces.manager')->setActiveWorkspace($workspace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches to the live version of the site for subsequent requests.
|
||||
*
|
||||
* This assumes that the switcher block has already been setup by calling
|
||||
* setupWorkspaceSwitcherBlock().
|
||||
*/
|
||||
protected function switchToLive() {
|
||||
/** @var \Drupal\Tests\WebAssert $session */
|
||||
$session = $this->assertSession();
|
||||
$this->submitForm([], 'Switch to Live');
|
||||
$session->pageTextContains('You are now viewing the live version of the site.');
|
||||
// Keep the test runner in sync with the system under test.
|
||||
\Drupal::service('workspaces.manager')->switchToLive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a node by "clicking" buttons.
|
||||
*
|
||||
* @param string $label
|
||||
* The label of the Node to create.
|
||||
* @param string $bundle
|
||||
* The bundle of the Node to create.
|
||||
* @param bool $publish
|
||||
* The publishing status to set.
|
||||
*
|
||||
* @return \Drupal\node\NodeInterface
|
||||
* The Node that was just created.
|
||||
*
|
||||
* @throws \Behat\Mink\Exception\ElementNotFoundException
|
||||
*/
|
||||
protected function createNodeThroughUi($label, $bundle, $publish = TRUE) {
|
||||
$this->drupalGet('/node/add/' . $bundle);
|
||||
|
||||
/** @var \Behat\Mink\Session $session */
|
||||
$session = $this->getSession();
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
/** @var \Behat\Mink\Element\DocumentElement $page */
|
||||
$page = $session->getPage();
|
||||
$page->fillField('Title', $label);
|
||||
if ($publish) {
|
||||
$page->findButton('Save')->click();
|
||||
}
|
||||
else {
|
||||
$page->uncheckField('Published');
|
||||
$page->findButton('Save')->click();
|
||||
}
|
||||
|
||||
$session->getPage()->hasContent("{$label} has been created");
|
||||
|
||||
return $this->getOneEntityByLabel('node', $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the content list has an entity's label.
|
||||
*
|
||||
* This assertion can be used to validate a particular entity exists in the
|
||||
* current workspace.
|
||||
*/
|
||||
protected function isLabelInContentOverview($label) {
|
||||
$this->drupalGet('/admin/content');
|
||||
$session = $this->getSession();
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$page = $session->getPage();
|
||||
return $page->hasContent($label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks an entity type as ignored in a workspace.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
*/
|
||||
protected function ignoreEntityType(string $entity_type_id): void {
|
||||
$entity_type = clone \Drupal::entityTypeManager()->getDefinition($entity_type_id);
|
||||
$entity_type->setHandlerClass('workspace', IgnoredWorkspaceHandler::class);
|
||||
\Drupal::state()->set("$entity_type_id.entity_type", $entity_type);
|
||||
\Drupal::entityTypeManager()->clearCachedDefinitions();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Tests permission controls on workspaces.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceViewTest extends BrowserTestBase {
|
||||
|
||||
use WorkspaceTestUtilities;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['workspaces', 'workspaces_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Verifies that a user can view their own workspace.
|
||||
*/
|
||||
public function testViewOwnWorkspace(): void {
|
||||
$permissions = [
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
'create workspace',
|
||||
'edit own workspace',
|
||||
'view own workspace',
|
||||
];
|
||||
|
||||
$editor1 = $this->drupalCreateUser($permissions);
|
||||
|
||||
// Login as a limited-access user and create a workspace.
|
||||
$this->drupalLogin($editor1);
|
||||
$this->createWorkspaceThroughUi('Bears', 'bears');
|
||||
|
||||
$bears = Workspace::load('bears');
|
||||
|
||||
// Now login as a different user and create a workspace.
|
||||
$editor2 = $this->drupalCreateUser($permissions);
|
||||
|
||||
$this->drupalLogin($editor2);
|
||||
$this->createWorkspaceThroughUi('Packers', 'packers');
|
||||
|
||||
$packers = Workspace::load('packers');
|
||||
|
||||
// Load the activate form for the Bears workspace. It should fail because
|
||||
// the workspace belongs to someone else.
|
||||
$this->drupalGet("admin/config/workflow/workspaces/manage/{$bears->id()}/activate");
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// But editor 2 should be able to activate the Packers workspace.
|
||||
$this->drupalGet("admin/config/workflow/workspaces/manage/{$packers->id()}/activate");
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a user can view any workspace.
|
||||
*/
|
||||
public function testViewAnyWorkspace(): void {
|
||||
$permissions = [
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
'create workspace',
|
||||
'edit own workspace',
|
||||
'view any workspace',
|
||||
];
|
||||
|
||||
$editor1 = $this->drupalCreateUser($permissions);
|
||||
|
||||
// Login as a limited-access user and create a workspace.
|
||||
$this->drupalLogin($editor1);
|
||||
|
||||
$this->createWorkspaceThroughUi('Bears', 'bears');
|
||||
|
||||
$bears = Workspace::load('bears');
|
||||
|
||||
// Now login as a different user and create a workspace.
|
||||
$editor2 = $this->drupalCreateUser($permissions);
|
||||
|
||||
$this->drupalLogin($editor2);
|
||||
$this->createWorkspaceThroughUi('Packers', 'packers');
|
||||
|
||||
$packers = Workspace::load('packers');
|
||||
|
||||
// Load the activate form for the Bears workspace. This user should be
|
||||
// able to see both workspaces because of the "view any" permission.
|
||||
$this->drupalGet("admin/config/workflow/workspaces/manage/{$bears->id()}/activate");
|
||||
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// But editor 2 should be able to activate the Packers workspace.
|
||||
$this->drupalGet("admin/config/workflow/workspaces/manage/{$packers->id()}/activate");
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional;
|
||||
|
||||
use Drupal\Tests\views\Functional\BulkFormTest;
|
||||
|
||||
/**
|
||||
* Tests the views bulk form in a workspace.
|
||||
*
|
||||
* @group views
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceViewsBulkFormTest extends BulkFormTest {
|
||||
|
||||
use WorkspaceTestUtilities;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block', 'workspaces', 'workspaces_ui', 'workspaces_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Override the user created in the parent method to add workspaces access.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer nodes',
|
||||
'administer workspaces',
|
||||
'edit any page content',
|
||||
'delete any page content',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Ensure that all the test methods are executed in the context of a
|
||||
// workspace.
|
||||
$this->setupWorkspaceSwitcherBlock();
|
||||
$this->createAndActivateWorkspaceThroughUi('Test workspace', 'test');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Workspaces view bulk form integration.
|
||||
*/
|
||||
public function testBulkForm(): void {
|
||||
// Ignore entity types that are not being tested, in order to fully re-use
|
||||
// the parent test method.
|
||||
$this->ignoreEntityType('view');
|
||||
|
||||
parent::testBulkForm();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests uninstalling the Workspaces module.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspacesUninstallTest extends BrowserTestBase {
|
||||
use ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['workspaces', 'node', 'workspaces_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$permissions = [
|
||||
'administer workspaces',
|
||||
'administer modules',
|
||||
];
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser($permissions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting workspace entities and uninstalling Workspaces module.
|
||||
*/
|
||||
public function testUninstallingWorkspace(): void {
|
||||
$this->createContentType(['type' => 'article']);
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->submitForm(['uninstall[workspaces_ui]' => TRUE], 'Uninstall');
|
||||
$this->submitForm([], 'Uninstall');
|
||||
$this->submitForm(['uninstall[workspaces]' => TRUE], 'Uninstall');
|
||||
$this->submitForm([], 'Uninstall');
|
||||
$session = $this->assertSession();
|
||||
$session->pageTextContains('The selected modules have been uninstalled.');
|
||||
$session->pageTextNotContains('Workspaces');
|
||||
|
||||
$this->assertFalse(\Drupal::database()->schema()->fieldExists('node_revision', 'workspace'));
|
||||
|
||||
// Verify that the revision metadata key has been removed.
|
||||
$this->rebuildContainer();
|
||||
$entity_type = \Drupal::entityDefinitionUpdateManager()->getEntityType('node');
|
||||
$revision_metadata_keys = $entity_type->get('revision_metadata_keys');
|
||||
$this->assertArrayNotHasKey('workspace', $revision_metadata_keys);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\FunctionalJavascript;
|
||||
|
||||
use Drupal\Tests\layout_builder\FunctionalJavascript\InlineBlockTestBase;
|
||||
use Drupal\Tests\system\Traits\OffCanvasTestTrait;
|
||||
use Drupal\Tests\workspaces\Functional\WorkspaceTestUtilities;
|
||||
use Drupal\user\UserInterface;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Tests for layout editing in workspaces.
|
||||
*
|
||||
* @group layout_builder
|
||||
* @group workspaces
|
||||
* @group #slow
|
||||
*/
|
||||
class WorkspacesLayoutBuilderIntegrationTest extends InlineBlockTestBase {
|
||||
|
||||
use OffCanvasTestTrait;
|
||||
use WorkspaceTestUtilities;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'starterkit_theme';
|
||||
|
||||
/**
|
||||
* The default user that is getting logged in during setup.
|
||||
*/
|
||||
protected UserInterface $defaultUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field_ui',
|
||||
'workspaces',
|
||||
'workspaces_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->defaultUser = $this->drupalCreateUser([
|
||||
'access contextual links',
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
'administer node fields',
|
||||
'create and edit custom blocks',
|
||||
'administer blocks',
|
||||
'administer content types',
|
||||
'administer workspaces',
|
||||
'view any workspace',
|
||||
'administer site configuration',
|
||||
'administer nodes',
|
||||
'bypass node access',
|
||||
]);
|
||||
$this->drupalLogin($this->defaultUser);
|
||||
$this->setupWorkspaceSwitcherBlock();
|
||||
Workspace::create(['id' => 'stage', 'label' => 'Stage'])->save();
|
||||
|
||||
// Enable layout builder.
|
||||
$this->drupalGet(static::FIELD_UI_PREFIX . '/display/default');
|
||||
$this->submitForm([
|
||||
'layout[enabled]' => TRUE,
|
||||
'layout[allow_custom]' => TRUE,
|
||||
], 'Save');
|
||||
$this->clickLink('Manage layout');
|
||||
$this->assertSession()->addressEquals(static::FIELD_UI_PREFIX . '/display/default/layout');
|
||||
// Add a basic block with the body field set.
|
||||
$this->addInlineBlockToLayout('Block title', 'The DEFAULT block body');
|
||||
$this->assertSaveLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests changing a layout/blocks inside a workspace.
|
||||
*/
|
||||
public function testBlocksInWorkspaces(): void {
|
||||
$assert_session = $this->assertSession();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$this->drupalGet('node/2');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
|
||||
$stage = Workspace::load('stage');
|
||||
$this->switchToWorkspace($stage);
|
||||
|
||||
// Confirm the block can be edited.
|
||||
$this->drupalGet('node/1/layout');
|
||||
$new_block_body = 'The NEW block body';
|
||||
$this->configureInlineBlock('The DEFAULT block body', $new_block_body);
|
||||
$this->assertSaveLayout();
|
||||
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains($new_block_body);
|
||||
$assert_session->pageTextNotContains('The DEFAULT block body');
|
||||
$this->drupalGet('node/2');
|
||||
// Node 2 should use default layout.
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$assert_session->pageTextNotContains($new_block_body);
|
||||
|
||||
// Switch back to the live workspace and verify that the changes are not
|
||||
// visible there.
|
||||
$this->switchToLive();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextNotContains($new_block_body);
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
|
||||
$this->switchToWorkspace($stage);
|
||||
// Add a basic block with the body field set.
|
||||
$this->drupalGet('node/1/layout');
|
||||
$second_block_body = 'The 2nd block body';
|
||||
$this->addInlineBlockToLayout('2nd Block title', $second_block_body);
|
||||
$this->assertSaveLayout();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains($second_block_body);
|
||||
$this->drupalGet('node/2');
|
||||
// Node 2 should use default layout.
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$assert_session->pageTextNotContains($new_block_body);
|
||||
$assert_session->pageTextNotContains($second_block_body);
|
||||
|
||||
// Switch back to the live workspace and verify that the new added block is
|
||||
// not visible there.
|
||||
$this->switchToLive();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextNotContains($second_block_body);
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
|
||||
// Check the concurrent editing protection on the Layout Builder form.
|
||||
$this->drupalGet('/node/1/layout');
|
||||
$assert_session->pageTextContains('The content is being edited in the Stage workspace. As a result, your changes cannot be saved.');
|
||||
|
||||
$stage->publish();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextNotContains('The DEFAULT block body');
|
||||
$assert_session->pageTextContains($new_block_body);
|
||||
$assert_session->pageTextContains($second_block_body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that blocks can be deleted inside workspaces.
|
||||
*/
|
||||
public function testBlockDeletionInWorkspaces(): void {
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
$stage = Workspace::load('stage');
|
||||
$this->switchToWorkspace($stage);
|
||||
|
||||
$this->drupalGet('node/1/layout');
|
||||
$workspace_block_content = 'The WORKSPACE block body';
|
||||
$this->addInlineBlockToLayout('Workspace block title', $workspace_block_content);
|
||||
$this->assertSaveLayout();
|
||||
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$assert_session->pageTextContains($workspace_block_content);
|
||||
|
||||
$this->switchToLive();
|
||||
$assert_session->pageTextNotContains($workspace_block_content);
|
||||
|
||||
$this->switchToWorkspace($stage);
|
||||
$this->drupalGet('node/1/layout');
|
||||
$this->removeInlineBlockFromLayout(static::INLINE_BLOCK_LOCATOR . ' ~ ' . static::INLINE_BLOCK_LOCATOR);
|
||||
$this->assertSaveLayout();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$assert_session->pageTextNotContains($workspace_block_content);
|
||||
|
||||
$this->drupalGet('node/1/layout');
|
||||
$this->removeInlineBlockFromLayout();
|
||||
$this->assertSaveLayout();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextNotContains('The DEFAULT block body');
|
||||
$assert_session->pageTextNotContains($workspace_block_content);
|
||||
|
||||
$this->switchToLive();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$stage->publish();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextNotContains('The DEFAULT block body');
|
||||
$assert_session->pageTextNotContains($workspace_block_content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests workspace specific layout tempstore data.
|
||||
*
|
||||
* @covers \Drupal\workspaces\WorkspacesLayoutTempstoreRepository::getKey
|
||||
*/
|
||||
public function testWorkspacesLayoutTempstore(): void {
|
||||
$assert_session = $this->assertSession();
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
|
||||
$second_user = $this->drupalCreateUser([
|
||||
'access contextual links',
|
||||
'configure any layout',
|
||||
'administer node display',
|
||||
'administer node fields',
|
||||
'create and edit custom blocks',
|
||||
'administer blocks',
|
||||
'administer content types',
|
||||
'administer workspaces',
|
||||
'view any workspace',
|
||||
'administer site configuration',
|
||||
'administer nodes',
|
||||
'bypass node access',
|
||||
]);
|
||||
|
||||
$stage = Workspace::load('stage');
|
||||
$this->switchToWorkspace($stage);
|
||||
|
||||
// Confirm the block can be edited.
|
||||
$this->drupalGet('node/1/layout');
|
||||
$workspace_block_body = 'The WS block body';
|
||||
$this->configureInlineBlock('The DEFAULT block body', $workspace_block_body);
|
||||
|
||||
// Switch to another user and check the layout edit page in the live
|
||||
// workspace and verify that the changes are not visible there. Switching
|
||||
// the user automatically switches the workspace to Live.
|
||||
$this->drupalLogin($second_user);
|
||||
$this->drupalGet('node/1/layout');
|
||||
$assert_session->pageTextNotContains($workspace_block_body);
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
$live_block_body = 'Live edit block body';
|
||||
$this->configureInlineBlock('The DEFAULT block body', $live_block_body);
|
||||
$assert_session->pageTextContains($live_block_body);
|
||||
$assert_session->pageTextNotContains('The DEFAULT block body');
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextNotContains($live_block_body);
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
|
||||
$this->drupalLogin($this->defaultUser);
|
||||
$this->switchToWorkspace($stage);
|
||||
$this->drupalGet('node/1/layout');
|
||||
$assert_session->pageTextContains($workspace_block_body);
|
||||
$assert_session->pageTextNotContains($live_block_body);
|
||||
$assert_session->pageTextNotContains('The DEFAULT block body');
|
||||
$this->drupalGet('node/1');
|
||||
$assert_session->pageTextNotContains($workspace_block_body);
|
||||
$assert_session->pageTextContains('The DEFAULT block body');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\FunctionalJavascript;
|
||||
|
||||
use Drupal\Tests\media_library\FunctionalJavascript\EntityReferenceWidgetTest;
|
||||
use Drupal\user\UserInterface;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Tests the Media library entity reference widget in a workspace.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspacesMediaLibraryIntegrationTest extends EntityReferenceWidgetTest {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'workspaces',
|
||||
];
|
||||
|
||||
/**
|
||||
* An array of test methods that are not relevant for workspaces.
|
||||
*/
|
||||
const SKIP_METHODS = [
|
||||
// This test does not assert anything that can be workspace-specific.
|
||||
'testFocusNotAppliedWithoutSelectionChange',
|
||||
// This test does not assert anything that can be workspace-specific.
|
||||
'testRequiredMediaField',
|
||||
// This test tries to edit an entity in Live after it has been edited in a
|
||||
// workspace, which is not currently possible.
|
||||
'testWidgetPreview',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp(): void {
|
||||
if (in_array($this->name(), static::SKIP_METHODS, TRUE)) {
|
||||
$this->markTestSkipped('Irrelevant for this test');
|
||||
}
|
||||
|
||||
parent::setUp();
|
||||
|
||||
// Ensure that all the test methods are executed in the context of a
|
||||
// workspace.
|
||||
$workspace = Workspace::create(['id' => 'test', 'label' => 'Test']);
|
||||
$workspace->save();
|
||||
\Drupal::service('workspaces.manager')->setActiveWorkspace($workspace);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function drupalCreateUser(array $permissions = [], $name = NULL, $admin = FALSE, array $values = []): UserInterface|false {
|
||||
// Ensure that users and roles are managed outside a workspace context.
|
||||
return \Drupal::service('workspaces.manager')->executeOutsideWorkspace(function () use ($permissions, $name, $admin, $values) {
|
||||
$permissions = array_merge($permissions, [
|
||||
'view any workspace',
|
||||
]);
|
||||
return parent::drupalCreateUser($permissions, $name, $admin, $values);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManager;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\entity_test\Entity\EntityTestMulRevPub;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\workspaces\Plugin\Validation\Constraint\EntityReferenceSupportedNewEntitiesConstraintValidator
|
||||
* @group workspaces
|
||||
*/
|
||||
class EntityReferenceSupportedNewEntitiesConstraintValidatorTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
use WorkspaceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'system',
|
||||
'user',
|
||||
'workspaces',
|
||||
'entity_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManager
|
||||
*/
|
||||
protected EntityTypeManager $entityTypeManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('user');
|
||||
$this->createUser();
|
||||
|
||||
$fields['supported_reference'] = BaseFieldDefinition::create('entity_reference')->setSetting('target_type', 'entity_test_mulrevpub');
|
||||
$fields['unsupported_reference'] = BaseFieldDefinition::create('entity_reference')->setSetting('target_type', 'entity_test');
|
||||
$this->container->get('state')->set('entity_test_mulrevpub.additional_base_field_definitions', $fields);
|
||||
|
||||
$this->installEntitySchema('entity_test_mulrevpub');
|
||||
$this->initializeWorkspacesModule();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::validate
|
||||
*/
|
||||
public function testNewEntitiesAllowedInDefaultWorkspace(): void {
|
||||
$entity = EntityTestMulRevPub::create([
|
||||
'unsupported_reference' => [
|
||||
'entity' => EntityTest::create([]),
|
||||
],
|
||||
'supported_reference' => [
|
||||
'entity' => EntityTest::create([]),
|
||||
],
|
||||
]);
|
||||
$this->assertCount(0, $entity->validate());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::validate
|
||||
*/
|
||||
public function testNewEntitiesForbiddenInNonDefaultWorkspace(): void {
|
||||
$this->switchToWorkspace('stage');
|
||||
$entity = EntityTestMulRevPub::create([
|
||||
'unsupported_reference' => [
|
||||
'entity' => EntityTest::create([]),
|
||||
],
|
||||
'supported_reference' => [
|
||||
'entity' => EntityTestMulRevPub::create([]),
|
||||
],
|
||||
]);
|
||||
$violations = $entity->validate();
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertEquals('Test entity entities can only be created in the default workspace.', $violations[0]->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\entity_test\Entity\EntityTestMulRevPub;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\workspaces\Plugin\Validation\Constraint\EntityWorkspaceConflictConstraintValidator
|
||||
* @group workspaces
|
||||
*/
|
||||
class EntityWorkspaceConflictConstraintValidatorTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
use WorkspaceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_test',
|
||||
'system',
|
||||
'user',
|
||||
'workspaces',
|
||||
];
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*/
|
||||
protected EntityTypeManagerInterface $entityTypeManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->entityTypeManager = \Drupal::entityTypeManager();
|
||||
|
||||
$this->installSchema('workspaces', ['workspace_association']);
|
||||
|
||||
$this->installEntitySchema('entity_test_mulrevpub');
|
||||
$this->installEntitySchema('workspace');
|
||||
$this->installEntitySchema('user');
|
||||
$this->createUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::validate
|
||||
*/
|
||||
public function testNewEntitiesAllowedInDefaultWorkspace(): void {
|
||||
// Create two top-level workspaces and a second-level one.
|
||||
$stage = Workspace::create(['id' => 'stage', 'label' => 'Stage']);
|
||||
$stage->save();
|
||||
$dev = Workspace::create(['id' => 'dev', 'label' => 'Dev', 'parent' => 'stage']);
|
||||
$dev->save();
|
||||
$other = Workspace::create(['id' => 'other', 'label' => 'Other']);
|
||||
$other->save();
|
||||
|
||||
// Create an entity in Live, and check that the validation is skipped.
|
||||
$entity = EntityTestMulRevPub::create();
|
||||
$this->assertCount(0, $entity->validate());
|
||||
$entity->save();
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$this->assertCount(0, $entity->validate());
|
||||
|
||||
// Edit the entity in Stage.
|
||||
$this->switchToWorkspace('stage');
|
||||
$entity->save();
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$this->assertCount(0, $entity->validate());
|
||||
|
||||
$expected_message = 'The content is being edited in the Stage workspace. As a result, your changes cannot be saved.';
|
||||
|
||||
// Check that the entity can no longer be edited in Live.
|
||||
$this->switchToLive();
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$violations = $entity->validate();
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertSame($expected_message, (string) $violations->get(0)->getMessage());
|
||||
|
||||
// Check that the entity can no longer be edited in another top-level
|
||||
// workspace.
|
||||
$this->switchToWorkspace('other');
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$violations = $entity->validate();
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertSame($expected_message, (string) $violations->get(0)->getMessage());
|
||||
|
||||
// Check that the entity can still be edited in a sub-workspace of Stage.
|
||||
$this->switchToWorkspace('dev');
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$this->assertCount(0, $entity->validate());
|
||||
|
||||
// Edit the entity in Dev.
|
||||
$this->switchToWorkspace('dev');
|
||||
$entity->save();
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$this->assertCount(0, $entity->validate());
|
||||
|
||||
$expected_message = 'The content is being edited in the Dev workspace. As a result, your changes cannot be saved.';
|
||||
|
||||
// Check that the entity can no longer be edited in Live.
|
||||
$this->switchToLive();
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$violations = $entity->validate();
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertSame($expected_message, (string) $violations->get(0)->getMessage());
|
||||
|
||||
// Check that the entity can no longer be edited in the parent workspace.
|
||||
$this->switchToWorkspace('stage');
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$violations = $entity->validate();
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertSame($expected_message, (string) $violations->get(0)->getMessage());
|
||||
|
||||
// Check that the entity can no longer be edited in another top-level
|
||||
// workspace.
|
||||
$this->switchToWorkspace('other');
|
||||
$entity = $this->reloadEntity($entity);
|
||||
$violations = $entity->validate();
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertSame($expected_message, (string) $violations->get(0)->getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the given entity from the storage and returns it.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to be reloaded.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* The reloaded entity.
|
||||
*/
|
||||
protected function reloadEntity(EntityInterface $entity): EntityInterface {
|
||||
$storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
|
||||
$storage->resetCache([$entity->id()]);
|
||||
return $storage->load($entity->id());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Kernel;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Tests access on workspaces.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceAccessTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'user',
|
||||
'system',
|
||||
'workspaces',
|
||||
'workspace_access_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installSchema('workspaces', ['workspace_association']);
|
||||
|
||||
$this->installEntitySchema('workspace');
|
||||
$this->installEntitySchema('user');
|
||||
|
||||
// User 1.
|
||||
$this->createUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests cases for testWorkspaceAccess().
|
||||
*
|
||||
* @return array
|
||||
* An array of operations and permissions to test with.
|
||||
*/
|
||||
public static function operationCases() {
|
||||
return [
|
||||
['create', 'administer workspaces'],
|
||||
['create', 'create workspace'],
|
||||
['view', 'administer workspaces'],
|
||||
['view', 'view any workspace'],
|
||||
['view', 'view own workspace'],
|
||||
['update', 'administer workspaces'],
|
||||
['update', 'edit any workspace'],
|
||||
['update', 'edit own workspace'],
|
||||
['delete', 'administer workspaces'],
|
||||
['delete', 'delete any workspace'],
|
||||
['delete', 'delete own workspace'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies all workspace roles have the correct access for the operation.
|
||||
*
|
||||
* @param string $operation
|
||||
* The operation to test with.
|
||||
* @param string $permission
|
||||
* The permission to test with.
|
||||
*
|
||||
* @dataProvider operationCases
|
||||
*/
|
||||
public function testWorkspaceAccess($operation, $permission): void {
|
||||
$user = $this->createUser();
|
||||
$this->setCurrentUser($user);
|
||||
$workspace = Workspace::create(['id' => 'oak']);
|
||||
$workspace->save();
|
||||
|
||||
$this->assertFalse($workspace->access($operation, $user));
|
||||
|
||||
\Drupal::entityTypeManager()->getAccessControlHandler('workspace')->resetCache();
|
||||
$role = $this->createRole([$permission]);
|
||||
$user->addRole($role);
|
||||
$this->assertTrue($workspace->access($operation, $user));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests workspace publishing access.
|
||||
*/
|
||||
public function testPublishWorkspaceAccess(): void {
|
||||
$user = $this->createUser([
|
||||
'view own workspace',
|
||||
'edit own workspace',
|
||||
]);
|
||||
$this->setCurrentUser($user);
|
||||
|
||||
$workspace = Workspace::create(['id' => 'stage']);
|
||||
$workspace->save();
|
||||
|
||||
// Check that, by default, an admin user is allowed to publish a workspace.
|
||||
$this->assertTrue($workspace->access('publish'));
|
||||
|
||||
// Simulate an external factor which decides that a workspace can not be
|
||||
// published.
|
||||
\Drupal::state()->set('workspace_access_test.result.publish', AccessResult::forbidden());
|
||||
\Drupal::entityTypeManager()->getAccessControlHandler('workspace')->resetCache();
|
||||
$this->assertFalse($workspace->access('publish'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\workspaces\Plugin\EntityReferenceSelection\WorkspaceSelection::getReferenceableEntities
|
||||
*/
|
||||
public function testWorkspaceSelection(): void {
|
||||
$own_permission_user = $this->createUser(['view own workspace']);
|
||||
$any_permission_user = $this->createUser(['view any workspace']);
|
||||
$admin_permission_user = $this->createUser(['administer workspaces']);
|
||||
|
||||
// Create the following workspace hierarchy:
|
||||
// - top1 ($own_permission_user)
|
||||
// --- child1_1 ($own_permission_user)
|
||||
// --- child1_2 ($any_permission_user)
|
||||
// ----- child1_2_1 ($any_permission_user)
|
||||
// - top2 ($admin_permission_user)
|
||||
// --- child2_1 ($admin_permission_user)
|
||||
$created_time = \Drupal::time()->getCurrentTime();
|
||||
Workspace::create([
|
||||
'uid' => $own_permission_user->id(),
|
||||
'id' => 'top1',
|
||||
'label' => 'top1',
|
||||
'created' => ++$created_time,
|
||||
])->save();
|
||||
Workspace::create([
|
||||
'uid' => $own_permission_user->id(),
|
||||
'id' => 'child1_1',
|
||||
'parent' => 'top1',
|
||||
'label' => 'child1_1',
|
||||
'created' => ++$created_time,
|
||||
])->save();
|
||||
Workspace::create([
|
||||
'uid' => $any_permission_user->id(),
|
||||
'id' => 'child1_2',
|
||||
'parent' => 'top1',
|
||||
'label' => 'child1_2',
|
||||
'created' => ++$created_time,
|
||||
])->save();
|
||||
Workspace::create([
|
||||
'uid' => $any_permission_user->id(),
|
||||
'id' => 'child1_2_1',
|
||||
'parent' => 'child1_2',
|
||||
'label' => 'child1_2_1',
|
||||
'created' => ++$created_time,
|
||||
])->save();
|
||||
Workspace::create([
|
||||
'uid' => $admin_permission_user->id(),
|
||||
'id' => 'top2',
|
||||
'label' => 'top2',
|
||||
'created' => ++$created_time,
|
||||
])->save();
|
||||
Workspace::create([
|
||||
'uid' => $admin_permission_user->id(),
|
||||
'id' => 'child2_1',
|
||||
'parent' => 'top2',
|
||||
'label' => 'child2_1',
|
||||
'created' => ++$created_time,
|
||||
])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface $selection_handler */
|
||||
$selection_handler = \Drupal::service('plugin.manager.entity_reference_selection')->getInstance([
|
||||
'target_type' => 'workspace',
|
||||
'handler' => 'default',
|
||||
'sort' => [
|
||||
'field' => 'created',
|
||||
'direction' => 'asc',
|
||||
],
|
||||
]);
|
||||
|
||||
// The $own_permission_user should only be allowed to reference 'top1' and
|
||||
// 'child1_1'.
|
||||
$this->setCurrentUser($own_permission_user);
|
||||
$expected = [
|
||||
'top1',
|
||||
'child1_1',
|
||||
];
|
||||
$this->assertEquals($expected, array_keys($selection_handler->getReferenceableEntities()['workspace']));
|
||||
$this->assertEquals($expected, array_keys($selection_handler->getReferenceableEntities(NULL, 'CONTAINS', 3)['workspace']));
|
||||
$expected = [
|
||||
'top1',
|
||||
];
|
||||
$this->assertEquals($expected, array_keys($selection_handler->getReferenceableEntities('top')['workspace']));
|
||||
|
||||
// The $any_permission_user and $admin_permission_user should be allowed to
|
||||
// reference any workspace.
|
||||
$expected_all = [
|
||||
'top1',
|
||||
'child1_1',
|
||||
'child1_2',
|
||||
'child1_2_1',
|
||||
'top2',
|
||||
'child2_1',
|
||||
];
|
||||
$expected_3 = [
|
||||
'top1',
|
||||
'child1_1',
|
||||
'child1_2',
|
||||
];
|
||||
$expected_top = [
|
||||
'top1',
|
||||
'top2',
|
||||
];
|
||||
$this->setCurrentUser($any_permission_user);
|
||||
$this->assertEquals($expected_all, array_keys($selection_handler->getReferenceableEntities()['workspace']));
|
||||
$this->assertEquals($expected_3, array_keys($selection_handler->getReferenceableEntities(NULL, 'CONTAINS', 3)['workspace']));
|
||||
$this->assertEquals($expected_top, array_keys($selection_handler->getReferenceableEntities('top')['workspace']));
|
||||
|
||||
$this->setCurrentUser($admin_permission_user);
|
||||
$this->assertEquals($expected_all, array_keys($selection_handler->getReferenceableEntities()['workspace']));
|
||||
$this->assertEquals($expected_3, array_keys($selection_handler->getReferenceableEntities(NULL, 'CONTAINS', 3)['workspace']));
|
||||
$this->assertEquals($expected_top, array_keys($selection_handler->getReferenceableEntities('top')['workspace']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\workspaces\Plugin\Block\WorkspaceSwitcherBlock::blockAccess
|
||||
*/
|
||||
public function testWorkspaceSwitcherBlock(): void {
|
||||
$own_permission_user = $this->createUser(['view own workspace']);
|
||||
$any_permission_user = $this->createUser(['view any workspace']);
|
||||
$admin_permission_user = $this->createUser(['administer workspaces']);
|
||||
$access_content_user = $this->createUser(['access content']);
|
||||
$no_permission_user = $this->createUser();
|
||||
|
||||
/** @var \Drupal\Core\Block\BlockManagerInterface $block_manager */
|
||||
$block_manager = \Drupal::service('plugin.manager.block');
|
||||
/** @var \Drupal\Core\Block\BlockPluginInterface $switcher_block */
|
||||
$switcher_block = $block_manager->createInstance('workspace_switcher');
|
||||
|
||||
$this->assertTrue($switcher_block->access($own_permission_user));
|
||||
$this->assertTrue($switcher_block->access($any_permission_user));
|
||||
$this->assertTrue($switcher_block->access($admin_permission_user));
|
||||
$this->assertFalse($switcher_block->access($access_content_user));
|
||||
$this->assertFalse($switcher_block->access($no_permission_user));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Tests workspace associations.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\workspaces\WorkspaceAssociation
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceAssociationTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
use WorkspaceTestTrait;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_test',
|
||||
'user',
|
||||
'system',
|
||||
'workspaces',
|
||||
'workspaces_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->entityTypeManager = \Drupal::entityTypeManager();
|
||||
|
||||
$this->installEntitySchema('entity_test_mulrevpub');
|
||||
$this->installEntitySchema('entity_test_mulrevpub_string_id');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('workspace');
|
||||
|
||||
$this->installConfig(['system']);
|
||||
|
||||
$this->installSchema('workspaces', ['workspace_association']);
|
||||
|
||||
$permissions = array_intersect([
|
||||
'administer nodes',
|
||||
'create workspace',
|
||||
'edit any workspace',
|
||||
'view any workspace',
|
||||
], array_keys($this->container->get('user.permissions')->getPermissions()));
|
||||
$this->setCurrentUser($this->createUser($permissions));
|
||||
|
||||
$this->workspaces['stage'] = Workspace::create(['id' => 'stage', 'label' => 'Stage']);
|
||||
$this->workspaces['stage']->save();
|
||||
$this->workspaces['dev'] = Workspace::create(['id' => 'dev', 'parent' => 'stage', 'label' => 'Dev']);
|
||||
$this->workspaces['dev']->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the revisions tracked by a workspace.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The ID of the entity type to test.
|
||||
* @param array $entity_values
|
||||
* An array of values for the entities created in this test.
|
||||
*
|
||||
* @covers ::getTrackedEntities
|
||||
* @covers ::getAssociatedRevisions
|
||||
*
|
||||
* @dataProvider getEntityTypeIds
|
||||
*/
|
||||
public function testWorkspaceAssociation(string $entity_type_id, array $entity_values): void {
|
||||
$entity_1 = $this->createEntity($entity_type_id, $entity_values[1]);
|
||||
$this->createEntity($entity_type_id, $entity_values[2]);
|
||||
|
||||
// Edit one of the existing nodes in 'stage'.
|
||||
$this->switchToWorkspace('stage');
|
||||
$entity_1->set('name', 'Test entity 1 - stage - published');
|
||||
$entity_1->setPublished();
|
||||
// This creates rev. 3.
|
||||
$entity_1->save();
|
||||
|
||||
// Generate content with the following structure:
|
||||
// Stage:
|
||||
// - Test entity 3 - stage - unpublished (rev. 4)
|
||||
// - Test entity 4 - stage - published (rev. 5 and 6)
|
||||
$this->createEntity($entity_type_id, $entity_values[3]);
|
||||
$this->createEntity($entity_type_id, $entity_values[4]);
|
||||
|
||||
$expected_latest_revisions = [
|
||||
'stage' => [3, 4, 6],
|
||||
];
|
||||
$expected_all_revisions = [
|
||||
'stage' => [3, 4, 5, 6],
|
||||
];
|
||||
$expected_initial_revisions = [
|
||||
'stage' => [4, 5],
|
||||
];
|
||||
$this->assertWorkspaceAssociations($entity_type_id, $expected_latest_revisions, $expected_all_revisions, $expected_initial_revisions);
|
||||
|
||||
// Dev:
|
||||
// - Test entity 1 - stage - published (rev. 3)
|
||||
// - Test entity 3 - stage - unpublished (rev. 4)
|
||||
// - Test entity 4 - stage - published (rev. 5 and 6)
|
||||
// - Test entity 5 - dev - unpublished (rev. 7)
|
||||
// - Test entity 6 - dev - published (rev. 8 and 9)
|
||||
$this->switchToWorkspace('dev');
|
||||
$this->createEntity($entity_type_id, $entity_values[5]);
|
||||
$this->createEntity($entity_type_id, $entity_values[6]);
|
||||
|
||||
$expected_latest_revisions += [
|
||||
'dev' => [3, 4, 6, 7, 9],
|
||||
];
|
||||
// Revisions 3, 4, 5 and 6 that were created in the parent 'stage' workspace
|
||||
// are also considered as being part of the child 'dev' workspace.
|
||||
$expected_all_revisions += [
|
||||
'dev' => [3, 4, 5, 6, 7, 8, 9],
|
||||
];
|
||||
$expected_initial_revisions += [
|
||||
'dev' => [7, 8],
|
||||
];
|
||||
$this->assertWorkspaceAssociations($entity_type_id, $expected_latest_revisions, $expected_all_revisions, $expected_initial_revisions);
|
||||
|
||||
// Merge 'dev' into 'stage' and check the workspace associations.
|
||||
/** @var \Drupal\workspaces\WorkspaceMergerInterface $workspace_merger */
|
||||
$workspace_merger = \Drupal::service('workspaces.operation_factory')->getMerger($this->workspaces['dev'], $this->workspaces['stage']);
|
||||
$workspace_merger->merge();
|
||||
|
||||
// The latest revisions from 'dev' are now tracked in 'stage'.
|
||||
$expected_latest_revisions['stage'] = $expected_latest_revisions['dev'];
|
||||
|
||||
// Two revisions (8 and 9) were created for 'Test article 6', but only the
|
||||
// latest one (9) is being merged into 'stage'.
|
||||
$expected_all_revisions['stage'] = [3, 4, 5, 6, 7, 9];
|
||||
|
||||
// Revision 7 was both an initial and latest revision in 'dev', so it is now
|
||||
// considered an initial revision in 'stage'.
|
||||
$expected_initial_revisions['stage'] = [4, 5, 7];
|
||||
|
||||
// Which leaves revision 8 as the only remaining initial revision in 'dev'.
|
||||
$expected_initial_revisions['dev'] = [8];
|
||||
|
||||
$this->assertWorkspaceAssociations($entity_type_id, $expected_latest_revisions, $expected_all_revisions, $expected_initial_revisions);
|
||||
|
||||
// Publish 'stage' and check the workspace associations.
|
||||
/** @var \Drupal\workspaces\WorkspacePublisherInterface $workspace_publisher */
|
||||
$workspace_publisher = \Drupal::service('workspaces.operation_factory')->getPublisher($this->workspaces['stage']);
|
||||
$workspace_publisher->publish();
|
||||
|
||||
$expected_revisions['stage'] = $expected_revisions['dev'] = [];
|
||||
$this->assertWorkspaceAssociations($entity_type_id, $expected_revisions, $expected_revisions, $expected_revisions);
|
||||
}
|
||||
|
||||
/**
|
||||
* The data provider for ::testWorkspaceAssociation().
|
||||
*/
|
||||
public static function getEntityTypeIds(): array {
|
||||
return [
|
||||
[
|
||||
'entity_type_id' => 'entity_test_mulrevpub',
|
||||
'entity_values' => [
|
||||
1 => ['name' => 'Test entity 1 - live - unpublished', 'status' => FALSE],
|
||||
2 => ['name' => 'Test entity 2 - live - published', 'status' => TRUE],
|
||||
3 => ['name' => 'Test entity 3 - stage - unpublished', 'status' => FALSE],
|
||||
4 => ['name' => 'Test entity 4 - stage - published', 'status' => TRUE],
|
||||
5 => ['name' => 'Test entity 5 - dev - unpublished', 'status' => FALSE],
|
||||
6 => ['name' => 'Test entity 6 - dev - published', 'status' => TRUE],
|
||||
],
|
||||
],
|
||||
[
|
||||
'entity_type_id' => 'entity_test_mulrevpub_string_id',
|
||||
'entity_values' => [
|
||||
1 => ['id' => 'test_1', 'name' => 'Test entity 1 - live - unpublished', 'status' => FALSE],
|
||||
2 => ['id' => 'test_2', 'name' => 'Test entity 2 - live - published', 'status' => TRUE],
|
||||
3 => ['id' => 'test_3', 'name' => 'Test entity 3 - stage - unpublished', 'status' => FALSE],
|
||||
4 => ['id' => 'test_4', 'name' => 'Test entity 4 - stage - published', 'status' => TRUE],
|
||||
5 => ['id' => 'test_5', 'name' => 'Test entity 5 - dev - unpublished', 'status' => FALSE],
|
||||
6 => ['id' => 'test_6', 'name' => 'Test entity 6 - dev - published', 'status' => TRUE],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the count of revisions returned for tracked entities listing.
|
||||
*
|
||||
* @covers ::getTrackedEntitiesForListing
|
||||
*/
|
||||
public function testWorkspaceAssociationForListing(): void {
|
||||
$this->switchToWorkspace($this->workspaces['stage']->id());
|
||||
$entity_type_id = 'entity_test_mulrevpub';
|
||||
|
||||
for ($i = 1; $i <= 51; ++$i) {
|
||||
$this->createEntity($entity_type_id, ['name' => "Test entity {$i}"]);
|
||||
}
|
||||
|
||||
/** @var \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association */
|
||||
$workspace_association = \Drupal::service('workspaces.association');
|
||||
|
||||
// The default behavior uses a pager with 50 items per page.
|
||||
$tracked_items = $workspace_association->getTrackedEntitiesForListing($this->workspaces['stage']->id());
|
||||
$this->assertEquals(50, count($tracked_items[$entity_type_id]));
|
||||
|
||||
// Verifies that all items are returned, not broken into pages.
|
||||
$tracked_items_no_pager = $workspace_association->getTrackedEntitiesForListing($this->workspaces['stage']->id(), NULL, FALSE);
|
||||
$this->assertEquals(51, count($tracked_items_no_pager[$entity_type_id]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the workspace associations for a test scenario.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The ID of the entity type that is being tested.
|
||||
* @param array $expected_latest_revisions
|
||||
* An array of expected values for the latest tracked revisions.
|
||||
* @param array $expected_all_revisions
|
||||
* An array of expected values for all the tracked revisions.
|
||||
* @param array $expected_initial_revisions
|
||||
* An array of expected values for the initial revisions, i.e. for the
|
||||
* entities that were created in the specified workspace.
|
||||
*/
|
||||
protected function assertWorkspaceAssociations($entity_type_id, array $expected_latest_revisions, array $expected_all_revisions, array $expected_initial_revisions): void {
|
||||
$workspace_association = \Drupal::service('workspaces.association');
|
||||
foreach ($expected_latest_revisions as $workspace_id => $expected_tracked_revision_ids) {
|
||||
$tracked_entities = $workspace_association->getTrackedEntities($workspace_id, $entity_type_id);
|
||||
$tracked_revision_ids = $tracked_entities[$entity_type_id] ?? [];
|
||||
$this->assertEquals($expected_tracked_revision_ids, array_keys($tracked_revision_ids));
|
||||
}
|
||||
|
||||
foreach ($expected_all_revisions as $workspace_id => $expected_all_revision_ids) {
|
||||
$all_associated_revisions = $workspace_association->getAssociatedRevisions($workspace_id, $entity_type_id);
|
||||
$this->assertEquals($expected_all_revision_ids, array_keys($all_associated_revisions));
|
||||
}
|
||||
|
||||
foreach ($expected_initial_revisions as $workspace_id => $expected_initial_revision_ids) {
|
||||
$initial_revisions = $workspace_association->getAssociatedInitialRevisions($workspace_id, $entity_type_id);
|
||||
$this->assertEquals($expected_initial_revision_ids, array_keys($initial_revisions));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,319 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
use Drupal\Tests\node\Traits\NodeCreationTrait;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Tests CRUD operations for workspaces.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceCRUDTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
use NodeCreationTrait;
|
||||
use ContentTypeCreationTrait;
|
||||
use WorkspaceTestTrait;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The state service.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* The workspace replication manager.
|
||||
*
|
||||
* @var \Drupal\workspaces\WorkspaceManagerInterface
|
||||
*/
|
||||
protected $workspaceManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'user',
|
||||
'system',
|
||||
'workspaces',
|
||||
'field',
|
||||
'filter',
|
||||
'node',
|
||||
'text',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpCurrentUser();
|
||||
|
||||
$this->installSchema('node', ['node_access']);
|
||||
|
||||
$this->installEntitySchema('workspace');
|
||||
$this->installSchema('workspaces', ['workspace_association']);
|
||||
$this->installEntitySchema('node');
|
||||
|
||||
$this->installConfig(['filter', 'node', 'system']);
|
||||
|
||||
$this->createContentType(['type' => 'page']);
|
||||
|
||||
$this->entityTypeManager = \Drupal::entityTypeManager();
|
||||
$this->state = \Drupal::state();
|
||||
$this->workspaceManager = \Drupal::service('workspaces.manager');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the deletion of workspaces.
|
||||
*/
|
||||
public function testDeletingWorkspaces(): void {
|
||||
$admin = $this->createUser([
|
||||
'administer nodes',
|
||||
'create workspace',
|
||||
'view any workspace',
|
||||
'edit any workspace',
|
||||
'delete any workspace',
|
||||
]);
|
||||
$this->setCurrentUser($admin);
|
||||
|
||||
/** @var \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association */
|
||||
$workspace_association = \Drupal::service('workspaces.association');
|
||||
|
||||
// Create a workspace with a very small number of associated node revisions.
|
||||
$workspace_1 = Workspace::create([
|
||||
'id' => 'gibbon',
|
||||
'label' => 'Gibbon',
|
||||
]);
|
||||
$workspace_1->save();
|
||||
$this->workspaceManager->setActiveWorkspace($workspace_1);
|
||||
|
||||
$workspace_1_node_1 = $this->createNode(['status' => FALSE]);
|
||||
$workspace_1_node_2 = $this->createNode(['status' => FALSE]);
|
||||
|
||||
// Check that the workspace tracks the initial revisions for both nodes.
|
||||
$initial_revisions = $workspace_association->getAssociatedInitialRevisions($workspace_1->id(), 'node');
|
||||
$this->assertCount(2, $initial_revisions);
|
||||
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$workspace_1_node_1->setNewRevision(TRUE);
|
||||
$workspace_1_node_1->save();
|
||||
|
||||
$workspace_1_node_2->setNewRevision(TRUE);
|
||||
$workspace_1_node_2->save();
|
||||
}
|
||||
|
||||
// The workspace should now track 2 nodes.
|
||||
$tracked_entities = $workspace_association->getTrackedEntities($workspace_1->id());
|
||||
$this->assertCount(2, $tracked_entities['node']);
|
||||
|
||||
// Since all the revisions were created inside a workspace, including the
|
||||
// default one, 'workspace_1' should be tracking all 10 revisions.
|
||||
$associated_revisions = $workspace_association->getAssociatedRevisions($workspace_1->id(), 'node');
|
||||
$this->assertCount(10, $associated_revisions);
|
||||
|
||||
// Check that we are allowed to delete the workspace.
|
||||
$this->assertTrue($workspace_1->access('delete', $admin));
|
||||
|
||||
// Delete the workspace and check that all the workspace_association
|
||||
// entities and all the node revisions have been deleted as well.
|
||||
$workspace_1->delete();
|
||||
|
||||
// There are no more tracked entities in 'workspace_1'.
|
||||
$tracked_entities = $workspace_association->getTrackedEntities($workspace_1->id());
|
||||
$this->assertEmpty($tracked_entities);
|
||||
|
||||
// There are no more revisions associated with 'workspace_1'.
|
||||
$associated_revisions = $workspace_association->getAssociatedRevisions($workspace_1->id(), 'node');
|
||||
$this->assertCount(0, $associated_revisions);
|
||||
|
||||
// Create another workspace, this time with a larger number of associated
|
||||
// node revisions so we can test the batch purge process.
|
||||
$workspace_2 = Workspace::create([
|
||||
'id' => 'baboon',
|
||||
'label' => 'Baboon',
|
||||
]);
|
||||
$workspace_2->save();
|
||||
$this->workspaceManager->setActiveWorkspace($workspace_2);
|
||||
|
||||
$workspace_2_node_1 = $this->createNode(['status' => FALSE]);
|
||||
for ($i = 0; $i < 59; $i++) {
|
||||
$workspace_2_node_1->setNewRevision(TRUE);
|
||||
$workspace_2_node_1->save();
|
||||
}
|
||||
|
||||
// Now there is one entity tracked in 'workspace_2'.
|
||||
$tracked_entities = $workspace_association->getTrackedEntities($workspace_2->id());
|
||||
$this->assertCount(1, $tracked_entities['node']);
|
||||
|
||||
// All 60 are associated with 'workspace_2'.
|
||||
$associated_revisions = $workspace_association->getAssociatedRevisions($workspace_2->id(), 'node', [$workspace_2_node_1->id()]);
|
||||
$this->assertCount(60, $associated_revisions);
|
||||
|
||||
// Delete the workspace and check that we still have 10 revision left to
|
||||
// delete.
|
||||
$workspace_2->delete();
|
||||
$associated_revisions = $workspace_association->getAssociatedRevisions($workspace_2->id(), 'node', [$workspace_2_node_1->id()]);
|
||||
$this->assertCount(10, $associated_revisions);
|
||||
|
||||
$workspace_deleted = \Drupal::state()->get('workspace.deleted');
|
||||
$this->assertCount(1, $workspace_deleted);
|
||||
|
||||
// Check that we can not create another workspace with the same ID while its
|
||||
// data purging is not finished.
|
||||
$workspace_3 = Workspace::create([
|
||||
'id' => 'baboon',
|
||||
'label' => 'Baboon',
|
||||
]);
|
||||
$violations = $workspace_3->validate();
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertEquals('A workspace with this ID has been deleted but data still exists for it.', $violations[0]->getMessage());
|
||||
|
||||
// Running cron should delete the remaining data as well as the workspace ID
|
||||
// from the "workspace.delete" state entry.
|
||||
\Drupal::service('cron')->run();
|
||||
|
||||
// Check that the actual node revisions were deleted as well.
|
||||
$node_storage = $this->entityTypeManager->getStorage('node');
|
||||
$this->assertEmpty($node_storage->loadMultipleRevisions(array_keys($associated_revisions)));
|
||||
|
||||
// 'workspace_2 'is empty now.
|
||||
$associated_revisions = $workspace_association->getAssociatedRevisions($workspace_2->id(), 'node', [$workspace_2_node_1->id()]);
|
||||
$this->assertCount(0, $associated_revisions);
|
||||
$tracked_entities = $workspace_association->getTrackedEntities($workspace_2->id());
|
||||
$this->assertCount(0, $tracked_entities);
|
||||
|
||||
$workspace_deleted = \Drupal::state()->get('workspace.deleted');
|
||||
$this->assertCount(0, $workspace_deleted);
|
||||
|
||||
// Check that the deleted workspace is no longer active.
|
||||
$this->assertFalse($this->workspaceManager->hasActiveWorkspace());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that deleting a workspace keeps its already published content.
|
||||
*/
|
||||
public function testDeletingPublishedWorkspace(): void {
|
||||
$admin = $this->createUser([
|
||||
'administer nodes',
|
||||
'create workspace',
|
||||
'view own workspace',
|
||||
'edit own workspace',
|
||||
'delete own workspace',
|
||||
]);
|
||||
$this->setCurrentUser($admin);
|
||||
|
||||
$live_workspace = Workspace::create([
|
||||
'id' => 'live',
|
||||
'label' => 'Live',
|
||||
]);
|
||||
$live_workspace->save();
|
||||
$workspace = Workspace::create([
|
||||
'id' => 'stage',
|
||||
'label' => 'Stage',
|
||||
]);
|
||||
$workspace->save();
|
||||
$this->workspaceManager->setActiveWorkspace($workspace);
|
||||
|
||||
// Create a new node in the 'stage' workspace
|
||||
$node = $this->createNode(['status' => TRUE]);
|
||||
|
||||
// Create an additional workspace-specific revision for the node.
|
||||
$node->setNewRevision(TRUE);
|
||||
$node->save();
|
||||
|
||||
// The node should have 3 revisions now: a default and 2 pending ones.
|
||||
$revisions = $this->entityTypeManager->getStorage('node')->loadMultipleRevisions([1, 2, 3]);
|
||||
$this->assertCount(3, $revisions);
|
||||
$this->assertTrue($revisions[1]->isDefaultRevision());
|
||||
$this->assertFalse($revisions[2]->isDefaultRevision());
|
||||
$this->assertFalse($revisions[3]->isDefaultRevision());
|
||||
|
||||
// Publish the workspace, which should mark revision 3 as the default one
|
||||
// and keep revision 2 as a 'source' draft revision.
|
||||
$workspace->publish();
|
||||
$revisions = $this->entityTypeManager->getStorage('node')->loadMultipleRevisions([1, 2, 3]);
|
||||
$this->assertFalse($revisions[1]->isDefaultRevision());
|
||||
$this->assertFalse($revisions[2]->isDefaultRevision());
|
||||
$this->assertTrue($revisions[3]->isDefaultRevision());
|
||||
|
||||
// Create two new workspace-revisions for the node.
|
||||
$node->setNewRevision(TRUE);
|
||||
$node->save();
|
||||
$node->setNewRevision(TRUE);
|
||||
$node->save();
|
||||
|
||||
// The node should now have 5 revisions.
|
||||
$revisions = $this->entityTypeManager->getStorage('node')->loadMultipleRevisions([1, 2, 3, 4, 5]);
|
||||
$this->assertFalse($revisions[1]->isDefaultRevision());
|
||||
$this->assertFalse($revisions[2]->isDefaultRevision());
|
||||
$this->assertTrue($revisions[3]->isDefaultRevision());
|
||||
$this->assertFalse($revisions[4]->isDefaultRevision());
|
||||
$this->assertFalse($revisions[5]->isDefaultRevision());
|
||||
|
||||
// Delete the workspace and check that only the two new pending revisions
|
||||
// were deleted by the workspace purging process.
|
||||
$workspace->delete();
|
||||
|
||||
$revisions = $this->entityTypeManager->getStorage('node')->loadMultipleRevisions([1, 2, 3, 4, 5]);
|
||||
$this->assertCount(3, $revisions);
|
||||
$this->assertFalse($revisions[1]->isDefaultRevision());
|
||||
$this->assertFalse($revisions[2]->isDefaultRevision());
|
||||
$this->assertTrue($revisions[3]->isDefaultRevision());
|
||||
$this->assertFalse(isset($revisions[4]));
|
||||
$this->assertFalse(isset($revisions[5]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a workspace with children can not be deleted.
|
||||
*/
|
||||
public function testDeletingWorkspaceWithChildren(): void {
|
||||
$stage = Workspace::create(['id' => 'stage', 'label' => 'Stage']);
|
||||
$stage->save();
|
||||
|
||||
$dev = Workspace::create(['id' => 'dev', 'label' => 'Dev', 'parent' => 'stage']);
|
||||
$dev->save();
|
||||
|
||||
// Check that a workspace which has children can not be deleted.
|
||||
try {
|
||||
$stage->delete();
|
||||
$this->fail('The Stage workspace has children and should not be deletable.');
|
||||
}
|
||||
catch (EntityStorageException $e) {
|
||||
$this->assertEquals('The Stage workspace can not be deleted because it has child workspaces.', $e->getMessage());
|
||||
$this->assertNotNull(Workspace::load('stage'));
|
||||
}
|
||||
|
||||
// Check that if we delete its child first, the parent workspace can also be
|
||||
// deleted.
|
||||
$dev->delete();
|
||||
$stage->delete();
|
||||
$this->assertNull(Workspace::load('dev'));
|
||||
$this->assertNull(Workspace::load('stage'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests loading the workspace tree when there are no workspaces available.
|
||||
*/
|
||||
public function testEmptyWorkspaceTree(): void {
|
||||
$tree = \Drupal::service('workspaces.repository')->loadTree();
|
||||
$this->assertSame([], $tree);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Tests entity translations with workspaces.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceContentTranslationTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
use WorkspaceTestTrait;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'content_translation',
|
||||
'entity_test',
|
||||
'language',
|
||||
'user',
|
||||
'workspaces',
|
||||
'workspaces_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->entityTypeManager = \Drupal::entityTypeManager();
|
||||
|
||||
$this->installEntitySchema('entity_test_mulrevpub');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('workspace');
|
||||
|
||||
$this->installConfig(['language', 'content_translation']);
|
||||
|
||||
$this->installSchema('workspaces', ['workspace_association']);
|
||||
|
||||
$language = ConfigurableLanguage::createFromLangcode('ro');
|
||||
$language->save();
|
||||
|
||||
$this->container->get('content_translation.manager')
|
||||
->setEnabled('entity_test_mulrevpub', 'entity_test_mulrevpub', TRUE);
|
||||
|
||||
Workspace::create(['id' => 'stage', 'label' => 'Stage'])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translations created in a workspace.
|
||||
*
|
||||
* @covers \Drupal\workspaces\Hook\EntityOperations::entityTranslationInsert
|
||||
*/
|
||||
public function testTranslations(): void {
|
||||
$storage = $this->entityTypeManager->getStorage('entity_test_mulrevpub');
|
||||
|
||||
// Create two untranslated nodes in Live, a published and an unpublished
|
||||
// one.
|
||||
$entity_published = $storage->create(['name' => 'live - 1 - published', 'status' => TRUE]);
|
||||
$entity_published->save();
|
||||
$entity_unpublished = $storage->create(['name' => 'live - 2 - unpublished', 'status' => FALSE]);
|
||||
$entity_unpublished->save();
|
||||
|
||||
// Activate the Stage workspace and add translations.
|
||||
$this->switchToWorkspace('stage');
|
||||
|
||||
// Add a translation for each entity.
|
||||
$entity_published->addTranslation('ro', ['name' => 'live - 1 - published - RO']);
|
||||
$entity_published->save();
|
||||
|
||||
// Test that the default revision translation is created in a WS.
|
||||
$this->assertTrue(\Drupal::keyValue('ws_test')->get('workspace_was_active'));
|
||||
|
||||
$entity_unpublished->addTranslation('ro', ['name' => 'live - 2 - unpublished - RO']);
|
||||
$entity_unpublished->save();
|
||||
|
||||
// Both 'EN' and 'RO' translations are published in Stage.
|
||||
$entity_published = $storage->loadUnchanged($entity_published->id());
|
||||
$this->assertTrue($entity_published->isPublished());
|
||||
$this->assertEquals('live - 1 - published', $entity_published->get('name')->value);
|
||||
|
||||
$translation = $entity_published->getTranslation('ro');
|
||||
$this->assertTrue($translation->isPublished());
|
||||
$this->assertEquals('live - 1 - published - RO', $translation->get('name')->value);
|
||||
|
||||
// Both 'EN' and 'RO' translations are unpublished in Stage.
|
||||
$entity_unpublished = $storage->loadUnchanged($entity_unpublished->id());
|
||||
$this->assertFalse($entity_unpublished->isPublished());
|
||||
$this->assertEquals('live - 2 - unpublished', $entity_unpublished->get('name')->value);
|
||||
|
||||
$translation = $entity_unpublished->getTranslation('ro');
|
||||
$this->assertEquals('live - 2 - unpublished - RO', $translation->get('name')->value);
|
||||
$this->assertTrue($translation->isPublished());
|
||||
|
||||
// Switch to Live and check the translations.
|
||||
$this->switchToLive();
|
||||
|
||||
// The 'EN' translation is still published in Live, but the 'RO' one is
|
||||
// unpublished.
|
||||
$entity_published = $storage->loadUnchanged($entity_published->id());
|
||||
$this->assertTrue($entity_published->isPublished());
|
||||
$this->assertEquals('live - 1 - published', $entity_published->get('name')->value);
|
||||
|
||||
$translation = $entity_published->getTranslation('ro');
|
||||
$this->assertFalse($translation->isPublished());
|
||||
$this->assertEquals('live - 1 - published - RO', $translation->get('name')->value);
|
||||
|
||||
// Both 'EN' and 'RO' translations are unpublished in Live.
|
||||
$entity_unpublished = $storage->loadUnchanged($entity_unpublished->id());
|
||||
$this->assertFalse($entity_unpublished->isPublished());
|
||||
$this->assertEquals('live - 2 - unpublished', $entity_unpublished->get('name')->value);
|
||||
|
||||
$translation = $entity_unpublished->getTranslation('ro');
|
||||
$this->assertFalse($translation->isPublished());
|
||||
$this->assertEquals('live - 2 - unpublished - RO', $translation->get('name')->value);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
use Drupal\Tests\node\Traits\NodeCreationTrait;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Tests entity deletions with workspaces.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceEntityDeleteTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
use NodeCreationTrait;
|
||||
use ContentTypeCreationTrait;
|
||||
use WorkspaceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field',
|
||||
'filter',
|
||||
'node',
|
||||
'system',
|
||||
'text',
|
||||
'user',
|
||||
'workspaces',
|
||||
];
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->entityTypeManager = \Drupal::entityTypeManager();
|
||||
$this->workspaceManager = \Drupal::service('workspaces.manager');
|
||||
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('workspace');
|
||||
|
||||
$this->installSchema('node', ['node_access']);
|
||||
$this->installSchema('workspaces', ['workspace_association']);
|
||||
|
||||
$this->installConfig(['filter', 'node', 'system']);
|
||||
|
||||
$this->createContentType(['type' => 'page']);
|
||||
|
||||
$this->setUpCurrentUser([], [
|
||||
'access content',
|
||||
'create page content',
|
||||
'edit any page content',
|
||||
'delete any page content',
|
||||
'create workspace',
|
||||
'view any workspace',
|
||||
'edit any workspace',
|
||||
'delete any workspace',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entity deletion in a workspace.
|
||||
*/
|
||||
public function testEntityDeletion(): void {
|
||||
/** @var \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association */
|
||||
$workspace_association = \Drupal::service('workspaces.association');
|
||||
$storage = $this->entityTypeManager->getStorage('node');
|
||||
|
||||
$published_live = $this->createNode(['title' => 'Test 1 published - live', 'type' => 'page']);
|
||||
$unpublished_live = $this->createNode(['title' => 'Test 2 unpublished - live', 'type' => 'page', 'status' => FALSE]);
|
||||
|
||||
// Create a published and an unpublished node in Stage.
|
||||
Workspace::create(['id' => 'stage', 'label' => 'Stage'])->save();
|
||||
$this->switchToWorkspace('stage');
|
||||
|
||||
$published_stage = $this->createNode(['title' => 'Test 3 published - stage', 'type' => 'page']);
|
||||
$unpublished_stage = $this->createNode([
|
||||
'title' => 'Test 4 unpublished - stage',
|
||||
'type' => 'page',
|
||||
'status' => FALSE,
|
||||
]);
|
||||
$this->assertEquals(['node' => [4 => 3, 5 => 4]], $workspace_association->getTrackedEntities('stage', 'node'));
|
||||
$this->assertTrue($published_stage->access('delete'));
|
||||
$this->assertTrue($unpublished_stage->access('delete'));
|
||||
|
||||
// While the Stage workspace is active, check that the nodes created in
|
||||
// Stage can be deleted, while the ones created in Live can not be deleted.
|
||||
$published_stage->delete();
|
||||
$this->assertEquals(['node' => [5 => 4]], $workspace_association->getTrackedEntities('stage', 'node'));
|
||||
|
||||
$unpublished_stage->delete();
|
||||
$this->assertEmpty($workspace_association->getTrackedEntities('stage', 'node'));
|
||||
$this->assertEmpty($storage->loadMultiple([$published_stage->id(), $unpublished_stage->id()]));
|
||||
|
||||
$this->expectExceptionMessage('This content item can only be deleted in the Live workspace.');
|
||||
$this->assertFalse($published_live->access('delete'));
|
||||
$this->assertFalse($unpublished_live->access('delete'));
|
||||
$published_live->delete();
|
||||
$unpublished_live->delete();
|
||||
$this->assertNotEmpty($storage->loadMultiple([$published_live->id(), $unpublished_live->id()]));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityRepositoryInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Tests the entity repository integration for workspaces.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceEntityRepositoryTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
use WorkspaceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_test',
|
||||
'language',
|
||||
'system',
|
||||
'user',
|
||||
'workspaces',
|
||||
];
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*/
|
||||
protected EntityTypeManagerInterface $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The entity repository.
|
||||
*/
|
||||
protected EntityRepositoryInterface $entityRepository;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->entityTypeManager = $this->container->get('entity_type.manager');
|
||||
$this->entityRepository = $this->container->get('entity.repository');
|
||||
|
||||
$this->installEntitySchema('entity_test_revpub');
|
||||
$this->installEntitySchema('entity_test_mulrevpub');
|
||||
$this->installEntitySchema('workspace');
|
||||
|
||||
$this->installSchema('workspaces', ['workspace_association']);
|
||||
|
||||
$this->installConfig(['system', 'language']);
|
||||
ConfigurableLanguage::createFromLangcode('ro')
|
||||
->setWeight(1)
|
||||
->save();
|
||||
|
||||
Workspace::create(['id' => 'ham', 'label' => 'Ham'])->save();
|
||||
Workspace::create(['id' => 'cheese', 'label' => 'Cheese'])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests retrieving active variants in a workspace.
|
||||
*
|
||||
* @covers \Drupal\Core\Entity\EntityRepository::getActive
|
||||
* @covers \Drupal\Core\Entity\EntityRepository::getActiveMultiple
|
||||
*/
|
||||
public function testGetActive(): void {
|
||||
$en_contexts = ['langcode' => 'en'];
|
||||
$ro_contexts = ['langcode' => 'ro'];
|
||||
|
||||
// Check that the correct active variant is returned for a non-translatable
|
||||
// revisionable entity.
|
||||
$entity_type_id = 'entity_test_revpub';
|
||||
$storage = $this->entityTypeManager->getStorage($entity_type_id);
|
||||
$values = ['name' => $this->randomString()];
|
||||
$entity = $storage->create($values);
|
||||
$storage->save($entity);
|
||||
|
||||
// Create revisions in two workspaces, then another one in Live.
|
||||
$this->switchToWorkspace('ham');
|
||||
$ham_revision = $storage->createRevision($entity);
|
||||
$storage->save($ham_revision);
|
||||
$active = $this->entityRepository->getActive($entity_type_id, $entity->id(), $en_contexts);
|
||||
$this->assertSame($ham_revision->getLoadedRevisionId(), $active->getLoadedRevisionId());
|
||||
|
||||
// Check that the active variant in Live is still the default revision.
|
||||
$this->switchToLive();
|
||||
$active = $this->entityRepository->getActive($entity_type_id, $entity->id(), $en_contexts);
|
||||
$this->assertSame($entity->getLoadedRevisionId(), $active->getLoadedRevisionId());
|
||||
|
||||
$this->switchToWorkspace('cheese');
|
||||
$cheese_revision = $storage->createRevision($entity);
|
||||
$storage->save($cheese_revision);
|
||||
$active = $this->entityRepository->getActive($entity_type_id, $entity->id(), $en_contexts);
|
||||
$this->assertSame($cheese_revision->getLoadedRevisionId(), $active->getLoadedRevisionId());
|
||||
|
||||
$this->switchToLive();
|
||||
$live_revision = $storage->createRevision($entity);
|
||||
$storage->save($live_revision);
|
||||
$active = $this->entityRepository->getActive($entity_type_id, $entity->id(), $en_contexts);
|
||||
$this->assertSame($live_revision->getLoadedRevisionId(), $active->getLoadedRevisionId());
|
||||
|
||||
// Switch back to the two workspaces and check that workspace-specific
|
||||
// revision are returned even when there's a newer revision in Live.
|
||||
$this->switchToWorkspace('ham');
|
||||
$active = $this->entityRepository->getActive($entity_type_id, $entity->id(), $en_contexts);
|
||||
$this->assertSame($ham_revision->getLoadedRevisionId(), $active->getLoadedRevisionId());
|
||||
|
||||
$this->switchToWorkspace('cheese');
|
||||
$active = $this->entityRepository->getActive($entity_type_id, $entity->id(), $en_contexts);
|
||||
$this->assertSame($cheese_revision->getLoadedRevisionId(), $active->getLoadedRevisionId());
|
||||
|
||||
// Check that a revision created in a workspace does not leak into other
|
||||
// workspaces.
|
||||
$entity_2 = $storage->create(['name' => $this->randomString()]);
|
||||
$storage->save($entity_2);
|
||||
|
||||
// Create a new revision in a workspace.
|
||||
$this->switchToWorkspace('ham');
|
||||
$ham_revision = $storage->createRevision($entity_2);
|
||||
$storage->save($ham_revision);
|
||||
$active = $this->entityRepository->getActive($entity_type_id, $entity_2->id(), $en_contexts);
|
||||
$this->assertSame($ham_revision->getLoadedRevisionId(), $active->getLoadedRevisionId());
|
||||
|
||||
// Check that the default revision is returned in another workspace.
|
||||
$this->switchToWorkspace('cheese');
|
||||
$active = $this->entityRepository->getActive($entity_type_id, $entity_2->id(), $en_contexts);
|
||||
$this->assertSame($entity_2->getLoadedRevisionId(), $active->getLoadedRevisionId());
|
||||
|
||||
// Check that the correct active variant is returned for a translatable and
|
||||
// revisionable entity.
|
||||
$this->switchToLive();
|
||||
$entity_type_id = 'entity_test_mulrevpub';
|
||||
$storage = $this->entityTypeManager->getStorage($entity_type_id);
|
||||
$values = ['name' => $this->randomString()];
|
||||
$initial_revision = $storage->create($values);
|
||||
$storage->save($initial_revision);
|
||||
|
||||
$revision_translation = $initial_revision->addTranslation('ro', $values);
|
||||
$revision_translation = $storage->createRevision($revision_translation);
|
||||
$storage->save($revision_translation);
|
||||
|
||||
// Add a translation in a workspace.
|
||||
$this->switchToWorkspace('ham');
|
||||
$ham_revision_ro = $storage->createRevision($revision_translation);
|
||||
$storage->save($ham_revision_ro);
|
||||
|
||||
$active = $this->entityRepository->getActive($entity_type_id, $entity->id(), $ro_contexts);
|
||||
$this->assertSame($ham_revision_ro->getLoadedRevisionId(), $active->getLoadedRevisionId());
|
||||
$this->assertSame($ham_revision_ro->language()->getId(), $active->language()->getId());
|
||||
|
||||
// Add a new translation in another workspace.
|
||||
$this->switchToWorkspace('cheese');
|
||||
$cheese_revision_ro = $storage->createRevision($revision_translation);
|
||||
$storage->save($cheese_revision_ro);
|
||||
|
||||
$active = $this->entityRepository->getActive($entity_type_id, $entity->id(), $ro_contexts);
|
||||
$this->assertSame($cheese_revision_ro->getLoadedRevisionId(), $active->getLoadedRevisionId());
|
||||
$this->assertSame($cheese_revision_ro->language()->getId(), $active->language()->getId());
|
||||
|
||||
// Add a new translations in Live.
|
||||
$this->switchToLive();
|
||||
$live_revision_ro = $storage->createRevision($revision_translation);
|
||||
$storage->save($live_revision_ro);
|
||||
|
||||
$active = $this->entityRepository->getActive($entity_type_id, $entity->id(), $ro_contexts);
|
||||
$this->assertSame($live_revision_ro->getLoadedRevisionId(), $active->getLoadedRevisionId());
|
||||
$this->assertSame($live_revision_ro->language()->getId(), $active->language()->getId());
|
||||
|
||||
$this->switchToWorkspace('ham');
|
||||
$active = $this->entityRepository->getActive($entity_type_id, $entity->id(), $ro_contexts);
|
||||
$this->assertSame($ham_revision_ro->getLoadedRevisionId(), $active->getLoadedRevisionId());
|
||||
$this->assertSame($ham_revision_ro->language()->getId(), $active->language()->getId());
|
||||
|
||||
$this->switchToWorkspace('cheese');
|
||||
$active = $this->entityRepository->getActive($entity_type_id, $entity->id(), $ro_contexts);
|
||||
$this->assertSame($cheese_revision_ro->getLoadedRevisionId(), $active->getLoadedRevisionId());
|
||||
$this->assertSame($cheese_revision_ro->language()->getId(), $active->language()->getId());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
|
||||
use Drupal\Core\Form\FormBuilderInterface;
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
use Drupal\workspaces_test\Form\ActiveWorkspaceTestForm;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Tests form persistence for the active workspace.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceFormPersistenceTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
use WorkspaceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'system',
|
||||
'user',
|
||||
'workspaces',
|
||||
'workspaces_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*/
|
||||
protected EntityTypeManagerInterface $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The form builder.
|
||||
*/
|
||||
protected FormBuilderInterface $formBuilder;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->entityTypeManager = \Drupal::entityTypeManager();
|
||||
$this->formBuilder = \Drupal::formBuilder();
|
||||
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('workspace');
|
||||
|
||||
Workspace::create(['id' => 'ham', 'label' => 'Ham'])->save();
|
||||
Workspace::create(['id' => 'cheese', 'label' => 'Cheese'])->save();
|
||||
|
||||
$this->setCurrentUser($this->createUser([
|
||||
'view any workspace',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the active workspace is persisted throughout a form's lifecycle.
|
||||
*/
|
||||
public function testFormPersistence(): void {
|
||||
$form_arg = ActiveWorkspaceTestForm::class;
|
||||
|
||||
$this->switchToWorkspace('ham');
|
||||
$form_state_1 = new FormState();
|
||||
$form_1 = $this->formBuilder->buildForm($form_arg, $form_state_1);
|
||||
|
||||
$this->switchToWorkspace('cheese');
|
||||
$form_state_2 = new FormState();
|
||||
$this->formBuilder->buildForm($form_arg, $form_state_2);
|
||||
|
||||
// Submit the second form and check the workspace in which it was submitted.
|
||||
$this->formBuilder->submitForm($form_arg, $form_state_2);
|
||||
$this->assertSame('cheese', $this->keyValue->get('ws_test')->get('form_test_active_workspace'));
|
||||
|
||||
// Submit the first form and check the workspace in which it was submitted.
|
||||
$this->formBuilder->submitForm($form_arg, $form_state_1);
|
||||
$this->assertSame('ham', $this->keyValue->get('ws_test')->get('form_test_active_workspace'));
|
||||
|
||||
// Reset the workspace manager service to simulate a new request and check
|
||||
// that the second workspace is still active.
|
||||
\Drupal::getContainer()->set('workspaces.manager', NULL);
|
||||
$this->assertSame('cheese', \Drupal::service('workspaces.manager')->getActiveWorkspace()->id());
|
||||
|
||||
// Reset the workspace manager service again to prepare for a new request.
|
||||
\Drupal::getContainer()->set('workspaces.manager', NULL);
|
||||
|
||||
$request = Request::create(
|
||||
$form_1['test']['#ajax']['url']->toString(),
|
||||
'POST',
|
||||
[
|
||||
MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax',
|
||||
] + $form_1['test']['#attached']['drupalSettings']['ajax'][$form_1['test']['#id']]['submit'],
|
||||
);
|
||||
\Drupal::service('http_kernel')->handle($request);
|
||||
|
||||
$form_state_1->setTriggeringElement($form_1['test']);
|
||||
\Drupal::service('form_ajax_response_builder')->buildResponse($request, $form_1, $form_state_1, []);
|
||||
|
||||
// Check that the AJAX callback is executed in the initial workspace of its
|
||||
// parent form.
|
||||
$this->assertSame('ham', $this->keyValue->get('ws_test')->get('ajax_test_active_workspace'));
|
||||
|
||||
// Reset the workspace manager service again and check that the AJAX request
|
||||
// didn't change the persisted workspace.
|
||||
\Drupal::getContainer()->set('workspaces.manager', NULL);
|
||||
\Drupal::requestStack()->pop();
|
||||
$this->assertSame('cheese', \Drupal::service('workspaces.manager')->getActiveWorkspace()->id());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use Drupal\workspaces\Entity\Handler\IgnoredWorkspaceHandler;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
use Drupal\workspaces_test\EntityTestRevPubWorkspaceHandler;
|
||||
|
||||
/**
|
||||
* Tests the workspace information service.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\workspaces\WorkspaceInformation
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceInformationTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
use WorkspaceTestTrait;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The workspace information service.
|
||||
*
|
||||
* @var \Drupal\wse\Core\WorkspaceInformationInterface
|
||||
*/
|
||||
protected $workspaceInformation;
|
||||
|
||||
/**
|
||||
* The state store.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_test',
|
||||
'user',
|
||||
'workspaces',
|
||||
'workspaces_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->entityTypeManager = \Drupal::entityTypeManager();
|
||||
$this->workspaceInformation = \Drupal::service('workspaces.information');
|
||||
$this->state = \Drupal::state();
|
||||
|
||||
$this->installEntitySchema('entity_test');
|
||||
$this->installEntitySchema('entity_test_rev');
|
||||
$this->installEntitySchema('entity_test_revpub');
|
||||
$this->installEntitySchema('workspace');
|
||||
|
||||
$this->installSchema('workspaces', ['workspace_association']);
|
||||
|
||||
// Create a new workspace and activate it.
|
||||
Workspace::create(['id' => 'stage', 'label' => 'Stage'])->save();
|
||||
$this->switchToWorkspace('stage');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests fully supported entity types.
|
||||
*/
|
||||
public function testSupportedEntityTypes(): void {
|
||||
// Check a supported entity type.
|
||||
$entity = $this->entityTypeManager->getStorage('entity_test_revpub')->create();
|
||||
|
||||
$this->assertTrue($this->workspaceInformation->isEntitySupported($entity));
|
||||
$this->assertTrue($this->workspaceInformation->isEntityTypeSupported($entity->getEntityType()));
|
||||
|
||||
$this->assertFalse($this->workspaceInformation->isEntityIgnored($entity));
|
||||
$this->assertFalse($this->workspaceInformation->isEntityTypeIgnored($entity->getEntityType()));
|
||||
|
||||
// Check that supported entity types are tracked in a workspace. This entity
|
||||
// is published by default, so the second revision will be tracked.
|
||||
$entity->save();
|
||||
$this->assertWorkspaceAssociation(['stage' => [2]], 'entity_test_revpub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests an entity type with a custom workspace handler.
|
||||
*/
|
||||
public function testCustomSupportEntityTypes(): void {
|
||||
$entity_type = clone $this->entityTypeManager->getDefinition('entity_test_revpub');
|
||||
$entity_type->setHandlerClass('workspace', EntityTestRevPubWorkspaceHandler::class);
|
||||
$this->state->set('entity_test_revpub.entity_type', $entity_type);
|
||||
$this->entityTypeManager->clearCachedDefinitions();
|
||||
|
||||
$entity = $this->entityTypeManager->getStorage('entity_test_revpub')->create([
|
||||
'type' => 'supported_bundle',
|
||||
]);
|
||||
|
||||
$this->assertTrue($this->workspaceInformation->isEntitySupported($entity));
|
||||
$this->assertTrue($this->workspaceInformation->isEntityTypeSupported($entity->getEntityType()));
|
||||
$this->assertFalse($this->workspaceInformation->isEntityIgnored($entity));
|
||||
$this->assertFalse($this->workspaceInformation->isEntityTypeIgnored($entity->getEntityType()));
|
||||
|
||||
// Check that supported entity types are tracked in a workspace. This entity
|
||||
// is published by default, so the second revision will be tracked.
|
||||
$entity->save();
|
||||
$this->assertWorkspaceAssociation(['stage' => [2]], 'entity_test_revpub');
|
||||
|
||||
$entity = $this->entityTypeManager->getStorage('entity_test_revpub')->create([
|
||||
'type' => 'ignored_bundle',
|
||||
]);
|
||||
|
||||
$this->assertFalse($this->workspaceInformation->isEntitySupported($entity));
|
||||
$this->assertTrue($this->workspaceInformation->isEntityTypeSupported($entity->getEntityType()));
|
||||
$this->assertTrue($this->workspaceInformation->isEntityIgnored($entity));
|
||||
$this->assertFalse($this->workspaceInformation->isEntityTypeIgnored($entity->getEntityType()));
|
||||
|
||||
// Check that an ignored entity can be saved, but won't be tracked.
|
||||
$entity->save();
|
||||
$this->assertWorkspaceAssociation(['stage' => [2]], 'entity_test_revpub');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ignored entity types.
|
||||
*/
|
||||
public function testIgnoredEntityTypes(): void {
|
||||
$entity_type = clone $this->entityTypeManager->getDefinition('entity_test_rev');
|
||||
$entity_type->setHandlerClass('workspace', IgnoredWorkspaceHandler::class);
|
||||
$this->state->set('entity_test_rev.entity_type', $entity_type);
|
||||
$this->entityTypeManager->clearCachedDefinitions();
|
||||
|
||||
// Check an ignored entity type. CRUD operations for an ignored entity type
|
||||
// are allowed in a workspace, but their revisions are not tracked.
|
||||
$entity = $this->entityTypeManager->getStorage('entity_test_rev')->create();
|
||||
$this->assertTrue($this->workspaceInformation->isEntityIgnored($entity));
|
||||
$this->assertTrue($this->workspaceInformation->isEntityTypeIgnored($entity->getEntityType()));
|
||||
|
||||
$this->assertFalse($this->workspaceInformation->isEntitySupported($entity));
|
||||
$this->assertFalse($this->workspaceInformation->isEntityTypeSupported($entity->getEntityType()));
|
||||
|
||||
// Check that ignored entity types are not tracked in a workspace.
|
||||
$entity->save();
|
||||
$this->assertWorkspaceAssociation(['stage' => []], 'entity_test_rev');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests unsupported entity types.
|
||||
*/
|
||||
public function testUnsupportedEntityTypes(): void {
|
||||
// Check an unsupported entity type.
|
||||
$entity_test = $this->entityTypeManager->getDefinition('entity_test');
|
||||
$this->assertFalse($entity_test->hasHandlerClass('workspace'));
|
||||
|
||||
$entity = $this->entityTypeManager->getStorage('entity_test')->create();
|
||||
$this->assertFalse($this->workspaceInformation->isEntitySupported($entity));
|
||||
$this->assertFalse($this->workspaceInformation->isEntityTypeSupported($entity_test));
|
||||
|
||||
$this->assertFalse($this->workspaceInformation->isEntityIgnored($entity));
|
||||
$this->assertFalse($this->workspaceInformation->isEntityTypeIgnored($entity_test));
|
||||
|
||||
// Check that unsupported entity types can not be saved in a workspace.
|
||||
$this->expectException(EntityStorageException::class);
|
||||
$this->expectExceptionMessage('The "entity_test" entity type can only be saved in the default workspace.');
|
||||
$entity->save();
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
use Drupal\Tests\node\Traits\NodeCreationTrait;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
|
||||
// cspell:ignore differring
|
||||
|
||||
/**
|
||||
* Tests workspace merging.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\workspaces\WorkspaceMerger
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceMergerTest extends KernelTestBase {
|
||||
|
||||
use ContentTypeCreationTrait;
|
||||
use NodeCreationTrait;
|
||||
use UserCreationTrait;
|
||||
use WorkspaceTestTrait;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* An array of nodes created before installing the Workspaces module.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface[]
|
||||
*/
|
||||
protected $nodes = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field',
|
||||
'filter',
|
||||
'node',
|
||||
'text',
|
||||
'user',
|
||||
'system',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->entityTypeManager = \Drupal::entityTypeManager();
|
||||
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('user');
|
||||
|
||||
$this->installConfig(['filter', 'node', 'system']);
|
||||
|
||||
$this->installSchema('node', ['node_access']);
|
||||
|
||||
$this->createContentType(['type' => 'article']);
|
||||
|
||||
$this->setCurrentUser($this->createUser(['administer nodes']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests workspace merging.
|
||||
*
|
||||
* @covers ::merge
|
||||
* @covers ::getNumberOfChangesOnSource
|
||||
* @covers ::getNumberOfChangesOnTarget
|
||||
* @covers ::getDifferringRevisionIdsOnSource
|
||||
* @covers ::getDifferringRevisionIdsOnTarget
|
||||
*/
|
||||
public function testWorkspaceMerger(): void {
|
||||
$this->initializeWorkspacesModule();
|
||||
$this->createWorkspaceHierarchy();
|
||||
|
||||
// Generate content in the workspace hierarchy with the following structure:
|
||||
// Live:
|
||||
// - Test article 1 - live
|
||||
//
|
||||
// Stage:
|
||||
// - Test article 2 - stage
|
||||
//
|
||||
// Dev:
|
||||
// - Test article 2 - stage
|
||||
// - Test article 3 - dev
|
||||
//
|
||||
// Local 1:
|
||||
// - Test article 2 - stage
|
||||
// - Test article 3 - dev
|
||||
// - Test article 4 - local_1
|
||||
//
|
||||
// Local 2:
|
||||
// - Test article 2 - stage
|
||||
// - Test article 3 - dev
|
||||
//
|
||||
// Note that the contents of each workspace are inherited automatically in
|
||||
// each of its descendants.
|
||||
$this->createNode(['title' => 'Test article 1 - live', 'type' => 'article']);
|
||||
|
||||
// This creates revisions 2 and 3. Revision 2 is an unpublished default
|
||||
// revision (which is also available in Live), and revision 3 is a published
|
||||
// pending revision that is available in Stage and all its descendants.
|
||||
$this->switchToWorkspace('stage');
|
||||
$this->createNode(['title' => 'Test article 2 - stage', 'type' => 'article']);
|
||||
|
||||
$expected_workspace_association = [
|
||||
'stage' => [3],
|
||||
'dev' => [3],
|
||||
'local_1' => [3],
|
||||
'local_2' => [3],
|
||||
'qa' => [],
|
||||
];
|
||||
$this->assertWorkspaceAssociation($expected_workspace_association, 'node');
|
||||
|
||||
// Create the second test article in Dev. This creates revisions 4 and 5.
|
||||
// Revision 4 is default and unpublished, and revision 5 is now being
|
||||
// tracked in Dev and its descendants.
|
||||
$this->switchToWorkspace('dev');
|
||||
$this->createNode(['title' => 'Test article 3 - dev', 'type' => 'article']);
|
||||
|
||||
$expected_workspace_association = [
|
||||
'stage' => [3],
|
||||
'dev' => [3, 5],
|
||||
'local_1' => [3, 5],
|
||||
'local_2' => [3, 5],
|
||||
'qa' => [],
|
||||
];
|
||||
$this->assertWorkspaceAssociation($expected_workspace_association, 'node');
|
||||
|
||||
// Create the third article in Local 1. This creates revisions 6 and 7.
|
||||
// Revision 6 is default and unpublished, and revision 7 is now being
|
||||
// tracked in the Local 1.
|
||||
$this->switchToWorkspace('local_1');
|
||||
$this->createNode(['title' => 'Test article 4 - local_1', 'type' => 'article']);
|
||||
|
||||
$expected_workspace_association = [
|
||||
'stage' => [3],
|
||||
'dev' => [3, 5],
|
||||
'local_1' => [3, 5, 7],
|
||||
'local_2' => [3, 5],
|
||||
'qa' => [],
|
||||
];
|
||||
$this->assertWorkspaceAssociation($expected_workspace_association, 'node');
|
||||
|
||||
/** @var \Drupal\workspaces\WorkspaceMergerInterface $workspace_merger */
|
||||
$workspace_merger = \Drupal::service('workspaces.operation_factory')->getMerger($this->workspaces['local_1'], $this->workspaces['dev']);
|
||||
|
||||
// Check that there is no content in Dev that's not also in Local 1.
|
||||
$this->assertEmpty($workspace_merger->getDifferringRevisionIdsOnTarget());
|
||||
$this->assertEquals(0, $workspace_merger->getNumberOfChangesOnTarget());
|
||||
|
||||
// Check that there is only one node in Local 1 that's not available in Dev,
|
||||
// revision 7 created above for the fourth test article.
|
||||
$expected = [
|
||||
'node' => [7 => 4],
|
||||
];
|
||||
$this->assertEquals($expected, $workspace_merger->getDifferringRevisionIdsOnSource());
|
||||
$this->assertEquals(1, $workspace_merger->getNumberOfChangesOnSource());
|
||||
|
||||
// Merge the contents of Local 1 into Dev, and check that Dev, Local 1 and
|
||||
// Local 2 have the same content.
|
||||
$workspace_merger->merge();
|
||||
|
||||
$this->assertEmpty($workspace_merger->getDifferringRevisionIdsOnTarget());
|
||||
$this->assertEquals(0, $workspace_merger->getNumberOfChangesOnTarget());
|
||||
$this->assertEmpty($workspace_merger->getDifferringRevisionIdsOnSource());
|
||||
$this->assertEquals(0, $workspace_merger->getNumberOfChangesOnSource());
|
||||
|
||||
$this->switchToWorkspace('dev');
|
||||
$expected_workspace_association = [
|
||||
'stage' => [3],
|
||||
'dev' => [3, 5, 7],
|
||||
'local_1' => [3, 5, 7],
|
||||
'local_2' => [3, 5, 7],
|
||||
'qa' => [],
|
||||
];
|
||||
$this->assertWorkspaceAssociation($expected_workspace_association, 'node');
|
||||
|
||||
$workspace_merger = \Drupal::service('workspaces.operation_factory')->getMerger($this->workspaces['local_1'], $this->workspaces['stage']);
|
||||
|
||||
// Check that there is no content in Stage that's not also in Local 1.
|
||||
$this->assertEmpty($workspace_merger->getDifferringRevisionIdsOnTarget());
|
||||
$this->assertEquals(0, $workspace_merger->getNumberOfChangesOnTarget());
|
||||
|
||||
// Check that the difference between Local 1 and Stage are the two revisions
|
||||
// for 'Test article 3 - dev' and 'Test article 4 - local_1'.
|
||||
$expected = [
|
||||
'node' => [
|
||||
5 => 3,
|
||||
7 => 4,
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $workspace_merger->getDifferringRevisionIdsOnSource());
|
||||
$this->assertEquals(2, $workspace_merger->getNumberOfChangesOnSource());
|
||||
|
||||
// Check that Local 1 can not be merged directly into Stage, since it can
|
||||
// only be merged into its direct parent.
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('The contents of a workspace can only be merged into its parent workspace.');
|
||||
$workspace_merger->merge();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\Core\Logger\RfcLogLevel;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
use Drupal\workspaces\Form\WorkspacePublishForm;
|
||||
use Drupal\workspaces\WorkspaceOperationFactory;
|
||||
use Drupal\workspaces\WorkspacePublisherInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\workspaces\Form\WorkspacePublishForm
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspacePublishFormTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* @covers ::submitForm
|
||||
*/
|
||||
public function testSubmitFormWithException(): void {
|
||||
/** @var \Drupal\Core\Messenger\MessengerInterface $messenger */
|
||||
$messenger = \Drupal::service('messenger');
|
||||
|
||||
$workspaceOperationFactory = $this->createMock(WorkspaceOperationFactory::class);
|
||||
$entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
/** @var \Drupal\Core\Logger\LoggerChannelFactoryInterface $loggerFactory */
|
||||
$loggerFactory = \Drupal::service('logger.factory');
|
||||
$loggerFactory->addLogger($logger);
|
||||
|
||||
$workspace = $this->createMock(Workspace::class);
|
||||
$workspacePublisher = $this->createMock(WorkspacePublisherInterface::class);
|
||||
|
||||
$workspace
|
||||
->expects($this->any())
|
||||
->method('label');
|
||||
|
||||
$workspace
|
||||
->expects($this->once())
|
||||
->method('publish')
|
||||
->willThrowException(new \Exception('Unexpected error'));
|
||||
|
||||
$workspaceOperationFactory
|
||||
->expects($this->once())
|
||||
->method('getPublisher')
|
||||
->willReturn($workspacePublisher);
|
||||
|
||||
$workspacePublisher
|
||||
->expects($this->once())
|
||||
->method('getTargetLabel');
|
||||
|
||||
$publishForm = new WorkspacePublishForm(
|
||||
$workspaceOperationFactory,
|
||||
$entityTypeManager
|
||||
);
|
||||
|
||||
$form = [];
|
||||
$formState = new FormState();
|
||||
|
||||
$publishForm->buildForm($form, $formState, $workspace);
|
||||
|
||||
$logger
|
||||
->expects($this->once())
|
||||
->method('log')
|
||||
->with(RfcLogLevel::ERROR, 'Unexpected error');
|
||||
|
||||
$publishForm->submitForm($form, $formState);
|
||||
|
||||
$messages = $messenger->messagesByType(MessengerInterface::TYPE_ERROR);
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertEquals('Publication failed. All errors have been logged.', $messages[0]);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Kernel;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Tests the query parameter workspace negotiator.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\workspaces\Negotiator\QueryParameterWorkspaceNegotiator
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceQueryParameterNegotiatorTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'user',
|
||||
'system',
|
||||
'workspaces',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('workspace');
|
||||
$this->installSchema('workspaces', ['workspace_association']);
|
||||
|
||||
// Create a new workspace for testing.
|
||||
Workspace::create(['id' => 'stage', 'label' => 'Stage'])->save();
|
||||
|
||||
$this->setCurrentUser($this->createUser(['administer workspaces']));
|
||||
|
||||
// Reset the internal state of the workspace manager so that checking for an
|
||||
// active workspace in the test is not influenced by previous actions.
|
||||
\Drupal::getContainer()->set('workspaces.manager', NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getActiveWorkspaceId
|
||||
* @dataProvider providerTestWorkspaceQueryParameter
|
||||
*/
|
||||
public function testWorkspaceQueryParameter(?string $workspace, ?string $token, ?string $negotiated_workspace, bool $has_active_workspace): void {
|
||||
// We can't access the settings service in the data provider method, so we
|
||||
// generate a good token here.
|
||||
if ($token === 'good_token') {
|
||||
$hash_salt = $this->container->get('settings')->get('hash_salt');
|
||||
$token = substr(Crypt::hmacBase64($workspace, $hash_salt), 0, 8);
|
||||
}
|
||||
|
||||
$request = \Drupal::request();
|
||||
$request->query->set('workspace', $workspace);
|
||||
$request->query->set('token', $token);
|
||||
|
||||
/** @var \Drupal\workspaces\Negotiator\QueryParameterWorkspaceNegotiator $negotiator */
|
||||
$negotiator = $this->container->get('workspaces.negotiator.query_parameter');
|
||||
|
||||
$this->assertSame($negotiated_workspace, $negotiator->getActiveWorkspaceId($request));
|
||||
$this->assertSame($has_active_workspace, \Drupal::service('workspaces.manager')->hasActiveWorkspace());
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testWorkspaceQueryParameter.
|
||||
*/
|
||||
public static function providerTestWorkspaceQueryParameter(): array {
|
||||
return [
|
||||
'no workspace, no token' => [
|
||||
'workspace' => NULL,
|
||||
'token' => NULL,
|
||||
'negotiated_workspace' => NULL,
|
||||
'has_active_workspace' => FALSE,
|
||||
],
|
||||
'fake workspace, no token' => [
|
||||
'workspace' => 'fake_id',
|
||||
'token' => NULL,
|
||||
'negotiated_workspace' => NULL,
|
||||
'has_active_workspace' => FALSE,
|
||||
],
|
||||
'fake workspace, fake token' => [
|
||||
'workspace' => 'fake_id',
|
||||
'token' => 'fake_token',
|
||||
'negotiated_workspace' => NULL,
|
||||
'has_active_workspace' => FALSE,
|
||||
],
|
||||
'good workspace, fake token' => [
|
||||
'workspace' => 'stage',
|
||||
'token' => 'fake_token',
|
||||
'negotiated_workspace' => NULL,
|
||||
'has_active_workspace' => FALSE,
|
||||
],
|
||||
// The fake workspace will be accepted by the negotiator in this case, but
|
||||
// the workspace manager will try to load and check access for it, and
|
||||
// won't set it as the active workspace. Note that "fake" can also mean a
|
||||
// workspace that existed at some point, then it was deleted and the user
|
||||
// is just accessing a stale link.
|
||||
'fake workspace, good token' => [
|
||||
'workspace' => 'fake_id',
|
||||
'token' => 'good_token',
|
||||
'negotiated_workspace' => 'fake_id',
|
||||
'has_active_workspace' => FALSE,
|
||||
],
|
||||
'good workspace, good token' => [
|
||||
'workspace' => 'stage',
|
||||
'token' => 'good_token',
|
||||
'negotiated_workspace' => 'stage',
|
||||
'has_active_workspace' => TRUE,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\workspaces\Entity\Handler\IgnoredWorkspaceHandler;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* A trait with common workspaces testing functionality.
|
||||
*/
|
||||
trait WorkspaceTestTrait {
|
||||
|
||||
/**
|
||||
* The workspaces manager.
|
||||
*
|
||||
* @var \Drupal\workspaces\WorkspaceManagerInterface
|
||||
*/
|
||||
protected $workspaceManager;
|
||||
|
||||
/**
|
||||
* An array of test workspaces, keyed by workspace ID.
|
||||
*
|
||||
* @var \Drupal\workspaces\WorkspaceInterface[]
|
||||
*/
|
||||
protected $workspaces = [];
|
||||
|
||||
/**
|
||||
* Enables the Workspaces module and creates two workspaces.
|
||||
*/
|
||||
protected function initializeWorkspacesModule() {
|
||||
// Enable the Workspaces module here instead of the static::$modules array
|
||||
// so we can test it with default content.
|
||||
$this->enableModules(['workspaces']);
|
||||
$this->container = \Drupal::getContainer();
|
||||
$this->entityTypeManager = \Drupal::entityTypeManager();
|
||||
$this->workspaceManager = \Drupal::service('workspaces.manager');
|
||||
|
||||
$this->installEntitySchema('workspace');
|
||||
$this->installSchema('workspaces', ['workspace_association']);
|
||||
|
||||
// Install the entity schema for supported entity types to ensure that the
|
||||
// 'workspace' revision metadata field gets created.
|
||||
foreach (array_keys(\Drupal::service('workspaces.information')->getSupportedEntityTypes()) as $entity_type_id) {
|
||||
$this->installEntitySchema($entity_type_id);
|
||||
}
|
||||
|
||||
// Create two workspaces by default, 'live' and 'stage'.
|
||||
$this->workspaces['live'] = Workspace::create(['id' => 'live', 'label' => 'Live']);
|
||||
$this->workspaces['live']->save();
|
||||
$this->workspaces['stage'] = Workspace::create(['id' => 'stage', 'label' => 'Stage']);
|
||||
$this->workspaces['stage']->save();
|
||||
|
||||
$permissions = array_intersect([
|
||||
'administer nodes',
|
||||
'create workspace',
|
||||
'edit any workspace',
|
||||
'view any workspace',
|
||||
], array_keys($this->container->get('user.permissions')->getPermissions()));
|
||||
$this->setCurrentUser($this->createUser($permissions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a given workspace as active.
|
||||
*
|
||||
* @param string $workspace_id
|
||||
* The ID of the workspace to switch to.
|
||||
*/
|
||||
protected function switchToWorkspace($workspace_id) {
|
||||
// Switch the test runner's context to the specified workspace.
|
||||
$workspace = $this->entityTypeManager->getStorage('workspace')->load($workspace_id);
|
||||
\Drupal::service('workspaces.manager')->setActiveWorkspace($workspace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the test runner's context to Live.
|
||||
*/
|
||||
protected function switchToLive(): void {
|
||||
\Drupal::service('workspaces.manager')->switchToLive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test workspace hierarchy.
|
||||
*
|
||||
* The full hierarchy including the default workspaces 'live' and 'stage' is:
|
||||
*
|
||||
* live
|
||||
* - stage
|
||||
* - dev
|
||||
* - local_1
|
||||
* - local_2
|
||||
* - qa
|
||||
*/
|
||||
protected function createWorkspaceHierarchy() {
|
||||
$this->workspaces['dev'] = Workspace::create(['id' => 'dev', 'parent' => 'stage', 'label' => 'dev']);
|
||||
$this->workspaces['dev']->save();
|
||||
$this->workspaces['local_1'] = Workspace::create(['id' => 'local_1', 'parent' => 'dev', 'label' => 'local_1']);
|
||||
$this->workspaces['local_1']->save();
|
||||
$this->workspaces['local_2'] = Workspace::create(['id' => 'local_2', 'parent' => 'dev', 'label' => 'local_2']);
|
||||
$this->workspaces['local_2']->save();
|
||||
$this->workspaces['qa'] = Workspace::create(['id' => 'qa', 'parent' => 'live', 'label' => 'qa']);
|
||||
$this->workspaces['qa']->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the workspace_association records for a test scenario.
|
||||
*
|
||||
* @param array $expected
|
||||
* An array of expected values, as defined in ::testWorkspaces().
|
||||
* @param string $entity_type_id
|
||||
* The ID of the entity type that is being tested.
|
||||
*/
|
||||
protected function assertWorkspaceAssociation(array $expected, $entity_type_id) {
|
||||
/** @var \Drupal\workspaces\WorkspaceAssociationInterface $workspace_association */
|
||||
$workspace_association = \Drupal::service('workspaces.association');
|
||||
foreach ($expected as $workspace_id => $expected_tracked_revision_ids) {
|
||||
$tracked_entities = $workspace_association->getTrackedEntities($workspace_id, $entity_type_id);
|
||||
$tracked_revision_ids = $tracked_entities[$entity_type_id] ?? [];
|
||||
$this->assertEquals($expected_tracked_revision_ids, array_keys($tracked_revision_ids));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the revisions which are not associated with any workspace.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* An entity type ID to find revisions for.
|
||||
* @param int[]|string[]|null $entity_ids
|
||||
* (optional) An array of entity IDs to filter the results by. Defaults to
|
||||
* NULL.
|
||||
*
|
||||
* @return array
|
||||
* An array of entity IDs, keyed by revision IDs.
|
||||
*/
|
||||
protected function getUnassociatedRevisions($entity_type_id, $entity_ids = NULL) {
|
||||
$entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
|
||||
|
||||
$query = \Drupal::entityTypeManager()
|
||||
->getStorage($entity_type_id)
|
||||
->getQuery()
|
||||
->allRevisions()
|
||||
->accessCheck(FALSE)
|
||||
->notExists($entity_type->get('revision_metadata_keys')['workspace']);
|
||||
|
||||
if ($entity_ids) {
|
||||
$query->condition($entity_type->getKey('id'), $entity_ids, 'IN');
|
||||
}
|
||||
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks an entity type as ignored in a workspace.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
*/
|
||||
protected function ignoreEntityType(string $entity_type_id): void {
|
||||
$entity_type = clone \Drupal::entityTypeManager()->getDefinition($entity_type_id);
|
||||
$entity_type->setHandlerClass('workspace', IgnoredWorkspaceHandler::class);
|
||||
\Drupal::state()->set("$entity_type_id.entity_type", $entity_type);
|
||||
\Drupal::entityTypeManager()->clearCachedDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an entity.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
* @param array $values
|
||||
* An array of values for the entity.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* The created entity.
|
||||
*/
|
||||
protected function createEntity(string $entity_type_id, array $values = []): EntityInterface {
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($entity_type_id);
|
||||
|
||||
$entity = $storage->create($values);
|
||||
$entity->save();
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
use Drupal\Tests\node\Traits\NodeCreationTrait;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\views_ui\ViewUI;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Tests the views integration for workspaces.
|
||||
*
|
||||
* @group views
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceViewsIntegrationTest extends ViewsKernelTestBase {
|
||||
|
||||
use ContentTypeCreationTrait;
|
||||
use EntityReferenceFieldCreationTrait;
|
||||
use NodeCreationTrait;
|
||||
use UserCreationTrait;
|
||||
use WorkspaceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'content_translation',
|
||||
'entity_test',
|
||||
'field',
|
||||
'filter',
|
||||
'node',
|
||||
'language',
|
||||
'text',
|
||||
'views_ui',
|
||||
'workspaces',
|
||||
];
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*/
|
||||
protected EntityTypeManagerInterface $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Creation timestamp that should be incremented for each new entity.
|
||||
*/
|
||||
protected int $createdTimestamp = 0;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE): void {
|
||||
parent::setUp(FALSE);
|
||||
|
||||
$this->entityTypeManager = \Drupal::entityTypeManager();
|
||||
|
||||
$this->installEntitySchema('entity_test_mulrevpub');
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('workspace');
|
||||
|
||||
$this->installConfig(['filter', 'node', 'system', 'language', 'content_translation']);
|
||||
|
||||
$this->installSchema('node', ['node_access']);
|
||||
$this->installSchema('workspaces', ['workspace_association']);
|
||||
|
||||
$language = ConfigurableLanguage::createFromLangcode('ro');
|
||||
$language->save();
|
||||
|
||||
$this->createContentType(['type' => 'page']);
|
||||
$this->container->get('content_translation.manager')->setEnabled('node', 'page', TRUE);
|
||||
|
||||
// Create an entity reference field, in order to test relationship queries.
|
||||
FieldStorageConfig::create([
|
||||
'entity_type' => 'node',
|
||||
'type' => 'entity_reference',
|
||||
'field_name' => 'field_reference',
|
||||
'settings' => [
|
||||
'target_type' => 'entity_test_mulrevpub',
|
||||
],
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'node',
|
||||
'bundle' => 'page',
|
||||
'field_name' => 'field_reference',
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests workspace query alter for views.
|
||||
*
|
||||
* @covers \Drupal\workspaces\Hook\ViewsOperations::alterQueryForEntityType
|
||||
* @covers \Drupal\workspaces\Hook\ViewsOperations::getRevisionTableJoin
|
||||
*/
|
||||
public function testViewsQueryAlter(): void {
|
||||
// Create a test entity and two nodes.
|
||||
$test_entity = \Drupal::entityTypeManager()
|
||||
->getStorage('entity_test_mulrevpub')
|
||||
->create(['name' => 'test entity - live']);
|
||||
$test_entity->save();
|
||||
$node_1 = $this->createNode([
|
||||
'title' => 'node - live - 1',
|
||||
'body' => 'node 1',
|
||||
'created' => $this->createdTimestamp++,
|
||||
'field_reference' => $test_entity->id(),
|
||||
]);
|
||||
$node_2 = $this->createNode([
|
||||
'title' => 'node - live - 2',
|
||||
'body' => 'node 2',
|
||||
'created' => $this->createdTimestamp++,
|
||||
]);
|
||||
|
||||
// Create a new workspace and activate it.
|
||||
Workspace::create(['id' => 'stage', 'label' => 'Stage'])->save();
|
||||
$this->switchToWorkspace('stage');
|
||||
|
||||
$view = Views::getView('frontpage');
|
||||
|
||||
// Add a filter on a field that is stored in a dedicated table in order to
|
||||
// test field joins with extra conditions (e.g. 'deleted' and 'langcode').
|
||||
$view->setDisplay('page_1');
|
||||
$filters = $view->displayHandlers->get('page_1')->getOption('filters');
|
||||
$view->displayHandlers->get('page_1')->overrideOption('filters', $filters + [
|
||||
'body_value' => [
|
||||
'id' => 'body_value',
|
||||
'table' => 'node__body',
|
||||
'field' => 'body_value',
|
||||
'operator' => 'not empty',
|
||||
'plugin_id' => 'string',
|
||||
],
|
||||
]);
|
||||
$view->execute();
|
||||
$expected = [
|
||||
['nid' => $node_2->id()],
|
||||
['nid' => $node_1->id()],
|
||||
];
|
||||
$this->assertIdenticalResultset($view, $expected, ['nid' => 'nid']);
|
||||
|
||||
// Add a filter on a field from a relationship, in order to test field
|
||||
// joins with extra conditions (e.g. 'deleted' and 'langcode').
|
||||
$view->destroy();
|
||||
$view->setDisplay('page_1');
|
||||
$view->displayHandlers->get('page_1')->overrideOption('relationships', [
|
||||
'field_reference' => [
|
||||
'id' => 'field_reference',
|
||||
'table' => 'node__field_reference',
|
||||
'field' => 'field_reference',
|
||||
'required' => FALSE,
|
||||
],
|
||||
]);
|
||||
$view->displayHandlers->get('page_1')->overrideOption('filters', $filters + [
|
||||
'name' => [
|
||||
'id' => 'name',
|
||||
'table' => 'entity_test_mulrevpub_property_data',
|
||||
'field' => 'name',
|
||||
'operator' => 'not empty',
|
||||
'relationship' => 'field_reference',
|
||||
],
|
||||
]);
|
||||
$view->execute();
|
||||
|
||||
$expected = [
|
||||
['nid' => $node_1->id()],
|
||||
];
|
||||
$this->assertIdenticalResultset($view, $expected, ['nid' => 'nid']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creating a view of workspace entities.
|
||||
*
|
||||
* @see \Drupal\views\Plugin\views\wizard\WizardPluginBase
|
||||
*/
|
||||
public function testCreateWorkspaceView(): void {
|
||||
$wizard = \Drupal::service('plugin.manager.views.wizard')->createInstance('standard:workspace', []);
|
||||
$form = [];
|
||||
$form_state = new FormState();
|
||||
$form = $wizard->buildForm($form, $form_state);
|
||||
$random_id = $this->randomMachineName();
|
||||
$random_label = $this->randomMachineName();
|
||||
|
||||
$form_state->setValues([
|
||||
'id' => $random_id,
|
||||
'label' => $random_label,
|
||||
'base_table' => 'workspace',
|
||||
]);
|
||||
|
||||
$wizard->validateView($form, $form_state);
|
||||
$view = $wizard->createView($form, $form_state);
|
||||
$this->assertInstanceOf(ViewUI::class, $view);
|
||||
$this->assertEquals($random_id, $view->get('id'));
|
||||
$this->assertEquals($random_label, $view->get('label'));
|
||||
$this->assertEquals('workspace', $view->get('base_table'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Tests\file\Kernel\FileItemTest;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
use Drupal\workspaces\Entity\Workspace;
|
||||
|
||||
/**
|
||||
* Tests using entity fields of the file field type in a workspace.
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspacesFileItemTest extends FileItemTest {
|
||||
|
||||
use UserCreationTrait;
|
||||
use WorkspaceTestTrait;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*/
|
||||
protected EntityTypeManagerInterface $entityTypeManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'file',
|
||||
'workspaces',
|
||||
'workspaces_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->entityTypeManager = \Drupal::entityTypeManager();
|
||||
|
||||
$this->installEntitySchema('workspace');
|
||||
$this->installSchema('workspaces', ['workspace_association']);
|
||||
|
||||
// Create a new workspace and activate it.
|
||||
Workspace::create(['id' => 'stage', 'label' => 'Stage'])->save();
|
||||
$this->switchToWorkspace('stage');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testFileItem(): void {
|
||||
// Ignore entity types that are not being tested, in order to fully re-use
|
||||
// the parent test method.
|
||||
$this->ignoreEntityType('entity_test');
|
||||
$this->ignoreEntityType('entity_view_display');
|
||||
|
||||
parent::testFileItem();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Unit;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Cache\Context\CacheContextsManager;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Drupal\workspaces\Access\ActiveWorkspaceCheck;
|
||||
use Drupal\workspaces\WorkspaceManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\workspaces\Access\ActiveWorkspaceCheck
|
||||
*
|
||||
* @group workspaces
|
||||
* @group Access
|
||||
*/
|
||||
class ActiveWorkspaceCheckTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The dependency injection container.
|
||||
*
|
||||
* @var \Symfony\Component\DependencyInjection\ContainerBuilder
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->container = new ContainerBuilder();
|
||||
$cache_contexts_manager = $this->prophesize(CacheContextsManager::class);
|
||||
$cache_contexts_manager->assertValidTokens()->willReturn(TRUE);
|
||||
$cache_contexts_manager->reveal();
|
||||
$this->container->set('cache_contexts_manager', $cache_contexts_manager);
|
||||
\Drupal::setContainer($this->container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides data for the testAccess method.
|
||||
*
|
||||
* @return array
|
||||
* An array of test data.
|
||||
*/
|
||||
public static function providerTestAccess() {
|
||||
return [
|
||||
[[], FALSE, FALSE],
|
||||
[[], TRUE, FALSE],
|
||||
[['_has_active_workspace' => 'TRUE'], TRUE, TRUE, ['workspace']],
|
||||
[['_has_active_workspace' => 'TRUE'], FALSE, FALSE, ['workspace']],
|
||||
[['_has_active_workspace' => 'FALSE'], TRUE, FALSE, ['workspace']],
|
||||
[['_has_active_workspace' => 'FALSE'], FALSE, TRUE, ['workspace']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::access
|
||||
* @dataProvider providerTestAccess
|
||||
*/
|
||||
public function testAccess($requirements, $has_active_workspace, $access, array $contexts = []): void {
|
||||
$route = new Route('', [], $requirements);
|
||||
|
||||
$workspace_manager = $this->prophesize(WorkspaceManagerInterface::class);
|
||||
$workspace_manager->hasActiveWorkspace()->willReturn($has_active_workspace);
|
||||
$access_check = new ActiveWorkspaceCheck($workspace_manager->reveal());
|
||||
|
||||
$access_result = AccessResult::allowedIf($access)->addCacheContexts($contexts);
|
||||
$this->assertEquals($access_result, $access_check->access($route));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\workspaces\Unit;
|
||||
|
||||
use Drupal\Core\Routing\CacheableRouteProviderInterface;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Drupal\workspaces\EventSubscriber\WorkspaceRequestSubscriber;
|
||||
use Drupal\workspaces\WorkspaceInterface;
|
||||
use Drupal\workspaces\WorkspaceManagerInterface;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\workspaces\EventSubscriber\WorkspaceRequestSubscriber
|
||||
*
|
||||
* @group workspaces
|
||||
*/
|
||||
class WorkspaceRequestSubscriberTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The workspace manager.
|
||||
*
|
||||
* @var \Drupal\workspaces\WorkspaceManagerInterface
|
||||
*/
|
||||
protected $workspaceManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->workspaceManager = $this->prophesize(WorkspaceManagerInterface::class);
|
||||
|
||||
$active_workspace = $this->prophesize(WorkspaceInterface::class);
|
||||
$active_workspace->id()->willReturn('test');
|
||||
$this->workspaceManager->getActiveWorkspace()->willReturn($active_workspace->reveal());
|
||||
$this->workspaceManager->hasActiveWorkspace()->willReturn(TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::onKernelRequest
|
||||
*/
|
||||
public function testOnKernelRequestWithCacheableRouteProvider(): void {
|
||||
$route_provider = $this->prophesize(CacheableRouteProviderInterface::class);
|
||||
$route_provider->addExtraCacheKeyPart('workspace', 'test')->shouldBeCalled();
|
||||
|
||||
// Check that WorkspaceRequestSubscriber::onKernelRequest() calls
|
||||
// addExtraCacheKeyPart() on a route provider that implements
|
||||
// CacheableRouteProviderInterface.
|
||||
$workspace_request_subscriber = new WorkspaceRequestSubscriber($route_provider->reveal(), $this->workspaceManager->reveal());
|
||||
$event = $this->prophesize(RequestEvent::class)->reveal();
|
||||
$this->assertNull($workspace_request_subscriber->onKernelRequest($event));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::onKernelRequest
|
||||
*/
|
||||
public function testOnKernelRequestWithoutCacheableRouteProvider(): void {
|
||||
$route_provider = $this->prophesize(RouteProviderInterface::class);
|
||||
|
||||
// Check that WorkspaceRequestSubscriber::onKernelRequest() doesn't call
|
||||
// addExtraCacheKeyPart() on a route provider that does not implement
|
||||
// CacheableRouteProviderInterface.
|
||||
$workspace_request_subscriber = new WorkspaceRequestSubscriber($route_provider->reveal(), $this->workspaceManager->reveal());
|
||||
$event = $this->prophesize(RequestEvent::class)->reveal();
|
||||
$this->assertNull($workspace_request_subscriber->onKernelRequest($event));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user