Issue #2729663 by dmsmidt, thpoul, SKAUGHT, Wim Leers, Lendude, droplet, effulgentsia, xjm, Reinmar, alexpott: Fragment link pointing to <textarea> should be redirected to CKEditor instance when CKEditor replaced that textarea

8.3.x
Nathaniel Catchpole 2016-12-14 14:30:20 +00:00
parent 98350348f3
commit 926f9dc603
4 changed files with 445 additions and 0 deletions

View File

@ -298,6 +298,20 @@
CKEDITOR.config.autoGrow_maxHeight = 0.7 * (window.innerHeight - displace.offsets.top - displace.offsets.bottom);
});
// Redirect on hash change when the original hash has an associated CKEditor.
function redirectTextareaFragmentToCKEditorInstance() {
var hash = location.hash.substr(1);
var element = document.getElementById(hash);
if (element) {
var editor = CKEDITOR.dom.element.get(element).getEditor();
if (editor) {
var id = editor.container.getAttribute('id');
location.replace('#' + id);
}
}
}
$(window).on('hashchange.ckeditor', redirectTextareaFragmentToCKEditorInstance);
// Set autoGrow to make the editor grow the moment it is created.
CKEDITOR.config.autoGrow_onStartup = true;

View File

@ -0,0 +1,121 @@
<?php
namespace Drupal\Tests\ckeditor\FunctionalJavascript;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\editor\Entity\Editor;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\filter\Entity\FilterFormat;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
use Drupal\node\Entity\NodeType;
/**
* Tests the integration of CKEditor.
*
* @group ckeditor
*/
class CKEditorIntegrationTest extends JavascriptTestBase {
/**
* The account.
*
* @var \Drupal\user\UserInterface
*/
protected $account;
/**
* {@inheritdoc}
*/
public static $modules = ['node', 'ckeditor', 'filter'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a text format and associate CKEditor.
$filtered_html_format = FilterFormat::create([
'format' => 'filtered_html',
'name' => 'Filtered HTML',
'weight' => 0,
]);
$filtered_html_format->save();
Editor::create([
'format' => 'filtered_html',
'editor' => 'ckeditor',
])->save();
// Create a node type for testing.
NodeType::create(['type' => 'page', 'name' => 'page'])->save();
$field_storage = FieldStorageConfig::loadByName('node', 'body');
// Create a body field instance for the 'page' node type.
FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'page',
'label' => 'Body',
'settings' => ['display_summary' => TRUE],
'required' => TRUE,
])->save();
// Assign widget settings for the 'default' form mode.
EntityFormDisplay::create([
'targetEntityType' => 'node',
'bundle' => 'page',
'mode' => 'default',
'status' => TRUE,
])->setComponent('body', ['type' => 'text_textarea_with_summary'])
->save();
$this->account = $this->drupalCreateUser([
'administer nodes',
'create page content',
'use text format filtered_html',
]);
$this->drupalLogin($this->account);
}
/**
* Tests if the fragment link to a textarea works with CKEditor enabled.
*/
public function testFragmentLink() {
$session = $this->getSession();
$web_assert = $this->assertSession();
$ckeditor_id = '#cke_edit-body-0-value';
$this->drupalGet('node/add/page');
$session->getPage();
// Add a bottom margin to the title field to be sure the body field is not
// visible. PhantomJS runs with a resolution of 1024x768px.
$session->executeScript("document.getElementById('edit-title-0-value').style.marginBottom = '800px';");
// Check that the CKEditor-enabled body field is currently not visible in
// the viewport.
$web_assert->assertNotVisibleInViewport('css', $ckeditor_id, 'topLeft', 'CKEditor-enabled body field is visible.');
$before_url = $session->getCurrentUrl();
// Trigger a hash change with as target the hidden textarea.
$session->executeScript("location.hash = '#edit-body-0-value';");
// Check that the CKEditor-enabled body field is visible in the viewport.
$web_assert->assertVisibleInViewport('css', $ckeditor_id, 'topLeft', 'CKEditor-enabled body field is not visible.');
// Use JavaScript to go back in the history instead of
// \Behat\Mink\Session::back() because that function doesn't work after a
// hash change.
$session->executeScript("history.back();");
$after_url = $session->getCurrentUrl();
// Check that going back in the history worked.
self::assertEquals($before_url, $after_url, 'History back works.');
}
}

View File

@ -0,0 +1,111 @@
<?php
namespace Drupal\Tests\inline_form_errors\FunctionalJavascript;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\editor\Entity\Editor;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\filter\Entity\FilterFormat;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
use Drupal\node\Entity\NodeType;
/**
* Tests the inline errors fragment link to a CKEditor-enabled textarea.
*
* @group ckeditor
*/
class FormErrorHandlerCKEditorTest extends JavascriptTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['node', 'ckeditor', 'inline_form_errors', 'filter'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create a text format and associate CKEditor.
$filtered_html_format = FilterFormat::create([
'format' => 'filtered_html',
'name' => 'Filtered HTML',
'weight' => 0,
]);
$filtered_html_format->save();
Editor::create([
'format' => 'filtered_html',
'editor' => 'ckeditor',
])->save();
// Create a node type for testing.
NodeType::create(['type' => 'page', 'name' => 'page'])->save();
$field_storage = FieldStorageConfig::loadByName('node', 'body');
// Create a body field instance for the 'page' node type.
FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'page',
'label' => 'Body',
'settings' => ['display_summary' => TRUE],
'required' => TRUE,
])->save();
// Assign widget settings for the 'default' form mode.
EntityFormDisplay::create([
'targetEntityType' => 'node',
'bundle' => 'page',
'mode' => 'default',
'status' => TRUE,
])->setComponent('body', ['type' => 'text_textarea_with_summary'])
->save();
$account = $this->drupalCreateUser([
'administer nodes',
'create page content',
'use text format filtered_html',
]);
$this->drupalLogin($account);
}
/**
* Tests if the fragment link to a textarea works with CKEditor enabled.
*/
public function testFragmentLink() {
$session = $this->getSession();
$web_assert = $this->assertSession();
$ckeditor_id = '#cke_edit-body-0-value';
$this->drupalGet('node/add/page');
$page = $this->getSession()->getPage();
// Only enter a title in the node add form and leave the body field empty.
$edit = ['edit-title-0-value' => 'Test inline form error with CKEditor'];
$this->submitForm($edit, 'Save and publish');
// Add a bottom margin to the title field to be sure the body field is not
// visible. PhantomJS runs with a resolution of 1024x768px.
$session->executeScript("document.getElementById('edit-title-0-value').style.marginBottom = '800px';");
// Check that the CKEditor-enabled body field is currently not visible in
// the viewport.
$web_assert->assertNotVisibleInViewport('css', $ckeditor_id, 'topLeft', 'CKEditor-enabled body field is not visible.');
// Check if we can find the error fragment link within the errors summary
// message.
$errors_link = $page->find('css', '.messages--error a[href=\#edit-body-0-value]');
$this->assertTrue($errors_link->isVisible(), 'Error fragment link is visible.');
$errors_link->click();
// Check that the CKEditor-enabled body field is visible in the viewport.
$web_assert->assertVisibleInViewport('css', $ckeditor_id, 'topLeft', 'CKEditor-enabled body field is visible.');
}
}

View File

@ -2,6 +2,10 @@
namespace Drupal\FunctionalJavascriptTests;
use Behat\Mink\Element\NodeElement;
use Behat\Mink\Exception\ElementHtmlException;
use Behat\Mink\Exception\ElementNotFoundException;
use Behat\Mink\Exception\UnsupportedDriverActionException;
use Drupal\Tests\WebAssert;
/**
@ -39,4 +43,199 @@ class JSWebAssert extends WebAssert {
$this->assertWaitOnAjaxRequest();
}
/**
* Test that a node, or it's specific corner, is visible in the viewport.
*
* Note: Always set the viewport size. This can be done with a PhantomJS
* startup parameter or in your test with \Behat\Mink\Session->resizeWindow().
* Drupal CI Javascript tests by default use a viewport of 1024x768px.
*
* @param string $selector_type
* The element selector type (CSS, XPath).
* @param string|array $selector
* The element selector. Note: the first found element is used.
* @param bool|string $corner
* (Optional) The corner to test:
* topLeft, topRight, bottomRight, bottomLeft.
* Or FALSE to check the complete element (default).
* @param string $message
* (optional) A message for the exception.
*
* @throws \Behat\Mink\Exception\ElementHtmlException
* When the element doesn't exist.
* @throws \Behat\Mink\Exception\ElementNotFoundException
* When the element is not visible in the viewport.
*/
public function assertVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is not visible in the viewport.') {
$node = $this->session->getPage()->find($selector_type, $selector);
if ($node === NULL) {
if (is_array($selector)) {
$selector = implode(' ', $selector);
}
throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
}
// Check if the node is visible on the page, which is a prerequisite of
// being visible in the viewport.
if (!$node->isVisible()) {
throw new ElementHtmlException($message, $this->session->getDriver(), $node);
}
$result = $this->checkNodeVisibilityInViewport($node, $corner);
if (!$result) {
throw new ElementHtmlException($message, $this->session->getDriver(), $node);
}
}
/**
* Test that a node, or its specific corner, is not visible in the viewport.
*
* Note: the node should exist in the page, otherwise this assertion fails.
*
* @param string $selector_type
* The element selector type (CSS, XPath).
* @param string|array $selector
* The element selector. Note: the first found element is used.
* @param bool|string $corner
* (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
* Or FALSE to check the complete element (default).
* @param string $message
* (optional) A message for the exception.
*
* @throws \Behat\Mink\Exception\ElementHtmlException
* When the element doesn't exist.
* @throws \Behat\Mink\Exception\ElementNotFoundException
* When the element is not visible in the viewport.
*
* @see \Drupal\FunctionalJavascriptTests\JSWebAssert::assertVisibleInViewport()
*/
public function assertNotVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is visible in the viewport.') {
$node = $this->session->getPage()->find($selector_type, $selector);
if ($node === NULL) {
if (is_array($selector)) {
$selector = implode(' ', $selector);
}
throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
}
$result = $this->checkNodeVisibilityInViewport($node, $corner);
if ($result) {
throw new ElementHtmlException($message, $this->session->getDriver(), $node);
}
}
/**
* Check the visibility of a node, or it's specific corner.
*
* @param \Behat\Mink\Element\NodeElement $node
* A valid node.
* @param bool|string $corner
* (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
* Or FALSE to check the complete element (default).
*
* @return bool
* Returns TRUE if the node is visible in the viewport, FALSE otherwise.
*
* @throws \Behat\Mink\Exception\UnsupportedDriverActionException
* When an invalid corner specification is given.
*/
private function checkNodeVisibilityInViewport(NodeElement $node, $corner = FALSE) {
$xpath = $node->getXpath();
// Build the Javascript to test if the complete element or a specific corner
// is in the viewport.
switch ($corner) {
case 'topLeft':
$test_javascript_function = <<<JS
function t(r, lx, ly) {
return (
r.top >= 0 &&
r.top <= ly &&
r.left >= 0 &&
r.left <= lx
)
}
JS;
break;
case 'topRight':
$test_javascript_function = <<<JS
function t(r, lx, ly) {
return (
r.top >= 0 &&
r.top <= ly &&
r.right >= 0 &&
r.right <= lx
);
}
JS;
break;
case 'bottomRight':
$test_javascript_function = <<<JS
function t(r, lx, ly) {
return (
r.bottom >= 0 &&
r.bottom <= ly &&
r.right >= 0 &&
r.right <= lx
);
}
JS;
break;
case 'bottomLeft':
$test_javascript_function = <<<JS
function t(r, lx, ly) {
return (
r.bottom >= 0 &&
r.bottom <= ly &&
r.left >= 0 &&
r.left <= lx
);
}
JS;
break;
case FALSE:
$test_javascript_function = <<<JS
function t(r, lx, ly) {
return (
r.top >= 0 &&
r.left >= 0 &&
r.bottom <= ly &&
r.right <= lx
);
}
JS;
break;
// Throw an exception if an invalid corner parameter is given.
default:
throw new UnsupportedDriverActionException($corner, $this->session->getDriver());
}
// Build the full Javascript test. The shared logic gets the corner
// specific test logic injected.
$full_javascript_visibility_test = <<<JS
(function(t){
var w = window,
d = document,
e = d.documentElement,
n = d.evaluate("$xpath", d, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue,
r = n.getBoundingClientRect(),
lx = (w.innerWidth || e.clientWidth),
ly = (w.innerHeight || e.clientHeight);
return t(r, lx, ly);
}($test_javascript_function));
JS;
// Check the visibility by injecting and executing the full Javascript test
// script in the page.
return $this->session->evaluateScript($full_javascript_visibility_test);
}
}