CKEditor4to5UpgradeCompletenessTest.php

Namespace

Drupal\Tests\ckeditor5\Kernel

File

core/modules/ckeditor5/tests/src/Kernel/CKEditor4to5UpgradeCompletenessTest.php

View source
<?php

declare (strict_types=1);
namespace Drupal\Tests\ckeditor5\Kernel;

use Drupal\ckeditor\CKEditorPluginConfigurableInterface;
use Drupal\ckeditor5\HTMLRestrictions;
use Drupal\ckeditor5\Plugin\CKEditor5PluginElementsSubsetInterface;
use Drupal\Component\Assertion\Inspector;
use Drupal\Component\Utility\NestedArray;
use Drupal\filter\Entity\FilterFormat;
use Drupal\KernelTests\KernelTestBase;

/**
 * @covers \Drupal\ckeditor5\Plugin\CKEditor4To5Upgrade\Core
 * @group ckeditor5
 * @internal
 */
class CKEditor4to5UpgradeCompletenessTest extends KernelTestBase {
    
    /**
     * The CKEditor 4 toolbar buttons that no longer require a contrib module.
     *
     * @var string[]
     *
     * @see \Drupal\ckeditor5\Plugin\CKEditor4To5Upgrade\Contrib
     */
    const CONTRIB_BUTTONS_NOW_IN_CORE = [
        // @see https://www.drupal.org/project/codetag
        // @see ckeditor5_code's `basicStyles.Code` plugin
'Code',
    ];
    
    /**
     * The "CKEditor 4 plugin" plugin manager.
     *
     * @var \Drupal\ckeditor\CKEditorPluginManager
     */
    protected $cke4PluginManager;
    
    /**
     * The "CKEditor 5 plugin" plugin manager.
     *
     * @var \Drupal\ckeditor5\Plugin\CKEditor5PluginManagerInterface
     */
    protected $cke5PluginManager;
    
    /**
     * The CKEditor 4 to 5 upgrade plugin manager.
     *
     * @var \Drupal\ckeditor5\Plugin\CKEditor4To5UpgradePluginManager
     */
    protected $upgradePluginManager;
    
    /**
     * Smart default settings utility.
     *
     * @var \Drupal\ckeditor5\SmartDefaultSettings
     */
    protected $smartDefaultSettings;
    
    /**
     * {@inheritdoc}
     */
    protected static $modules = [
        'ckeditor',
        'ckeditor5',
        // Enabled because of ::testCKEditor5ConfigurableSubsetPlugins().
'filter',
        // Enabled because of \Drupal\media\Plugin\CKEditorPlugin\DrupalMedia.
'media',
        // Enabled because of \Drupal\media_library\Plugin\CKEditorPlugin\DrupalMediaLibrary.
'media_library',
        // Enabled for media_library.
'views',
        // These modules must be installed for ckeditor5_config_schema_info_alter()
        // to work, which in turn is necessary for the plugin definition validation
        // logic.
        // @see \Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition::validateDrupalAspects()
'filter',
        'editor',
    ];
    
    /**
     * {@inheritdoc}
     */
    protected function setUp() : void {
        parent::setUp();
        // The tested service is private; expose it under a public test-only alias.
        $this->container
            ->setAlias('sut', 'plugin.manager.ckeditor4to5upgrade.plugin');
        $this->cke4PluginManager = $this->container
            ->get('plugin.manager.ckeditor.plugin');
        $this->cke5PluginManager = $this->container
            ->get('plugin.manager.ckeditor5.plugin');
        $this->upgradePluginManager = $this->container
            ->get('sut');
        $this->smartDefaultSettings = $this->container
            ->get('ckeditor5.smart_default_settings');
    }
    
    /**
     * Tests that all CKEditor 4 buttons in core have an upgrade path.
     */
    public function testButtons() : void {
        $cke4_buttons = array_keys(NestedArray::mergeDeepArray($this->cke4PluginManager
            ->getButtons()));
        $cke4_buttons = array_merge($cke4_buttons, self::CONTRIB_BUTTONS_NOW_IN_CORE);
        foreach ($cke4_buttons as $button) {
            $equivalent = $this->upgradePluginManager
                ->mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem($button, HTMLRestrictions::emptySet());
            $this->assertTrue($equivalent === NULL || is_array($equivalent) && Inspector::assertAllStrings($equivalent));
            // The returned equivalent CKEditor 5 toolbar item(s) must exist.
            if (is_string($equivalent)) {
                foreach (explode(',', $equivalent) as $equivalent_cke5_toolbar_item) {
                    $this->assertArrayHasKey($equivalent_cke5_toolbar_item, $this->cke5PluginManager
                        ->getToolbarItems());
                }
            }
        }
    }
    
    /**
     * Tests that the test-only CKEditor 4 module does not have an upgrade path.
     */
    public function testButtonsWithTestOnlyModule() : void {
        $this->enableModules([
            'ckeditor_test',
        ]);
        $this->cke4PluginManager = $this->container
            ->get('plugin.manager.ckeditor.plugin');
        $this->expectException(\OutOfBoundsException::class);
        $this->expectExceptionMessage('No upgrade path found for the "LlamaCSS" button.');
        $this->testButtons();
    }
    
    /**
     * Tests that all configurable CKEditor 4 plugins in core have an upgrade path.
     */
    public function testSettings() : void {
        $cke4_configurable_plugins = [];
        foreach ($this->cke4PluginManager
            ->getDefinitions() as $plugin_id => $definition) {
            // Special case: DrupalImage.
            // @see \Drupal\ckeditor\Plugin\CKEditorPlugin\DrupalImage
            // @see \Drupal\editor\Entity\Editor::getImageUploadSettings()
            if ($plugin_id === 'drupalimage') {
                continue;
            }
            if (is_subclass_of($definition['class'], CKEditorPluginConfigurableInterface::class)) {
                $cke4_configurable_plugins[] = $plugin_id;
            }
        }
        foreach ($cke4_configurable_plugins as $plugin_id) {
            $cke5_plugin_settings = $this->upgradePluginManager
                ->mapCKEditor4SettingsToCKEditor5Configuration($plugin_id, []);
            $this->assertTrue($cke5_plugin_settings === NULL || is_array($cke5_plugin_settings));
            // The returned equivalent CKEditor 5 plugin must exist.
            if (is_array($cke5_plugin_settings)) {
                $cke5_plugin_id = array_keys($cke5_plugin_settings)[0];
                $this->assertArrayHasKey($cke5_plugin_id, $this->cke5PluginManager
                    ->getDefinitions());
            }
        }
    }
    
    /**
     * Tests that the test-only CKEditor 4 module does not have an upgrade path.
     */
    public function testSettingsWithTestOnlyModule() : void {
        $this->enableModules([
            'ckeditor_test',
        ]);
        $this->cke4PluginManager = $this->container
            ->get('plugin.manager.ckeditor.plugin');
        $this->expectException(\OutOfBoundsException::class);
        $this->expectExceptionMessage('No upgrade path found for the "llama_contextual_and_button" plugin settings.');
        $this->testSettings();
    }
    
    /**
     * Tests that all elements subset plugins in core have an upgrade path.
     */
    public function testCKEditor5ConfigurableSubsetPlugins() : void {
        $cke5_elements_subset_plugins = [];
        foreach ($this->cke5PluginManager
            ->getDefinitions() as $plugin_id => $definition) {
            // Special case: SourceEditing.
            // @see \Drupal\ckeditor5\SmartDefaultSettings::computeSubsetSettingForEnabledPluginsWithSubsets()
            if ($plugin_id === 'ckeditor5_sourceEditing') {
                continue;
            }
            if (is_a($definition->getClass(), CKEditor5PluginElementsSubsetInterface::class, TRUE)) {
                $cke5_elements_subset_plugins[] = $plugin_id;
            }
        }
        foreach ($cke5_elements_subset_plugins as $plugin_id) {
            $cke5_plugin_configuration = $this->upgradePluginManager
                ->computeCKEditor5PluginSubsetConfiguration($plugin_id, FilterFormat::create());
            $this->assertTrue($cke5_plugin_configuration === NULL || is_array($cke5_plugin_configuration));
        }
    }
    
    /**
     * Tests that only one plugin can provide an upgrade path for a button.
     */
    public function testOnlyOneUpgradePluginAllowedPerCKEditor4Button() : void {
        $this->enableModules([
            'ckeditor4to5upgrade_plugin_test',
        ]);
        \Drupal::state()->set('ckeditor4to5upgrade_plugin_test', 'duplicate_button');
        $this->expectException(\OutOfBoundsException::class);
        $this->expectExceptionMessage('The "DrupalImage" CKEditor 4 button is already being upgraded by the "core" CKEditor4To5Upgrade plugin, the "foo" plugin is as well. This conflict needs to be resolved.');
        $this->upgradePluginManager
            ->mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem('foo', HTMLRestrictions::emptySet());
    }
    
    /**
     * Tests detecting a lying upgrade plugin cke4_button annotation.
     */
    public function testLyingUpgradePluginForCKEditor4Button() : void {
        $this->enableModules([
            'ckeditor4to5upgrade_plugin_test',
        ]);
        \Drupal::state()->set('ckeditor4to5upgrade_plugin_test', 'lying_button');
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The "foo" CKEditor4To5Upgrade plugin claims to provide an upgrade path for the "foo" CKEditor 4 button but does not.');
        $this->upgradePluginManager
            ->mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem('foo', HTMLRestrictions::emptySet());
    }
    
    /**
     * Tests that only one plugin can provide an upgrade path for plugin settings.
     */
    public function testOnlyOneUpgradePluginAllowedPerCKEditor4PluginSettings() : void {
        $this->enableModules([
            'ckeditor4to5upgrade_plugin_test',
        ]);
        \Drupal::state()->set('ckeditor4to5upgrade_plugin_test', 'duplicate_plugin_settings');
        $this->expectException(\OutOfBoundsException::class);
        $this->expectExceptionMessage('The "stylescombo" CKEditor 4 plugin\'s settings are already being upgraded by the "core" CKEditor4To5Upgrade plugin, the "foo" plugin is as well. This conflict needs to be resolved.');
        $this->upgradePluginManager
            ->mapCKEditor4ToolbarButtonToCKEditor5ToolbarItem('foo', HTMLRestrictions::emptySet());
    }
    
    /**
     * Tests detecting a lying upgrade plugin cke4_plugin_settings annotation.
     */
    public function testLyingUpgradePluginForCKEditor4PluginSettings() : void {
        $this->enableModules([
            'ckeditor4to5upgrade_plugin_test',
        ]);
        \Drupal::state()->set('ckeditor4to5upgrade_plugin_test', 'lying_plugin_settings');
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The "foo" CKEditor4To5Upgrade plugin claims to provide an upgrade path for the "foo" CKEditor 4 plugin settings but does not.');
        $this->upgradePluginManager
            ->mapCKEditor4SettingsToCKEditor5Configuration('foo', []);
    }
    
    /**
     * Tests that only one plugin can provide an upgrade path for a subset plugin.
     */
    public function testOnlyOneUpgradePluginAllowedPerCKEditor5ConfigurableSubsetPlugin() : void {
        $this->enableModules([
            'ckeditor4to5upgrade_plugin_test',
        ]);
        \Drupal::state()->set('ckeditor4to5upgrade_plugin_test', 'duplicate_subset');
        $this->expectException(\OutOfBoundsException::class);
        $this->expectExceptionMessage('The "ckeditor5_heading" CKEditor 5 plugin\'s elements subset configuration is already being computed by the "core" CKEditor4To5Upgrade plugin, the "foo" plugin is as well. This conflict needs to be resolved.');
        $this->upgradePluginManager
            ->computeCKEditor5PluginSubsetConfiguration('foo', FilterFormat::create());
    }
    
    /**
     * Tests detecting lying cke5_plugin_elements_subset_configuration annotation.
     */
    public function testLyingUpgradePluginForCKEditor5ConfigurableSubsetPlugin() : void {
        $this->enableModules([
            'ckeditor4to5upgrade_plugin_test',
        ]);
        \Drupal::state()->set('ckeditor4to5upgrade_plugin_test', 'lying_subset');
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The "foo" CKEditor4To5Upgrade plugin claims to provide an upgrade path for the "foo" CKEditor 4 plugin settings but does not.');
        $this->upgradePluginManager
            ->computeCKEditor5PluginSubsetConfiguration('foo', FilterFormat::create());
    }

}

Classes

Title Deprecated Summary
CKEditor4to5UpgradeCompletenessTest @covers \Drupal\ckeditor5\Plugin\CKEditor4To5Upgrade\Core @group ckeditor5 @internal

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.