diff --git a/modules/oe_theme_helper/js/table_sort.js b/modules/oe_theme_helper/js/table_sort.js new file mode 100644 index 000000000..461edf13d --- /dev/null +++ b/modules/oe_theme_helper/js/table_sort.js @@ -0,0 +1,94 @@ +/** + * @file + * Plugin for adding sorting of table. + */ + +(function ($, CKEDITOR) { + "use strict"; + + CKEDITOR.plugins.add('table_sort', { + afterInit: function afterInit (editor) { + CKEDITOR.on('dialogDefinition', function(event) { + const dialog_name = event.data.name; + const sort_attribute = 'data-sortable'; + + if (dialog_name !== 'cellProperties') { + return; + } + + const dialog_definition = event.data.definition; + const info_tab = dialog_definition.getContents('info'); + const langCell = event.editor.lang.table.cell; + // Avoid multiple selectbox adding. + if (!info_tab.get('columnSortable')) { + info_tab.add({ + type: 'select', + label: 'Sortable', + id: 'columnSortable', + requiredContent: 'th[' + sort_attribute + ']', + items: [ + [langCell.yes, 'yes'], + [langCell.no, 'no'], + ], + + /** + * @param {CKEDITOR.dom.element[]} selectedCells + */ + setup: function (selectedCells) { + // Disable the element if any of the selected cells is not a header cell. + for (let i = 0; i < selectedCells.length; i++) { + if (selectedCells[i].getName() !== 'th') { + this.disable(); + this.setValue(null); + return; + } + } + + // This method receives an array of selected cells. + // We use the value of the first one as driver for the remaining cells. + // @see setupCells() in /plugins/tabletools/dialogs/tableCell.js + let value = selectedCells[0].hasAttribute(sort_attribute); + + for (let i = 1; i < selectedCells.length; i++) { + if (selectedCells[i].hasAttribute(sort_attribute) !== value) { + // If any of the cells has a different sortable value, set the value + // to undetermined. + value = null; + break; + } + } + + // Convert the value to the matching option. + if (value !== null) { + value = value ? 'yes' : 'no'; + } + + this.setValue(value); + + // The only way to have an empty select value in Firefox is + // to set a negative selectedIndex. + if (value === null && CKEDITOR.env.gecko) { + this.getInputElement().$.selectedIndex = -1; + } + }, + + /** + * @param {CKEDITOR.dom.element} selectedCell + */ + commit: function (selectedCell) { + const value = this.getValue(); + + // Handle only supported values. + if (value === 'yes') { + selectedCell.setAttribute(sort_attribute, true); + } else if (value === 'no') { + selectedCell.removeAttribute(sort_attribute); + } + } + }); + } + }); + } + }); + +})(jQuery, CKEDITOR); diff --git a/modules/oe_theme_helper/js/table_zebra_striping.js b/modules/oe_theme_helper/js/table_zebra_striping.js new file mode 100644 index 000000000..9762f5da3 --- /dev/null +++ b/modules/oe_theme_helper/js/table_zebra_striping.js @@ -0,0 +1,45 @@ +/** + * @file + * Plugin for adding zebra striping of table. + */ + +(function ($, CKEDITOR) { + "use strict"; + + CKEDITOR.plugins.add('table_zebra_striping', { + afterInit: function afterInit (editor) { + CKEDITOR.on('dialogDefinition', function(event) { + const dialog_name = event.data.name; + + if (dialog_name !== 'table' && dialog_name !== 'tableProperties') { + return; + } + + const dialog_definition = event.data.definition; + const info_tab = dialog_definition.getContents('info'); + const zebra_attribute = 'data-striped'; + // Avoid multiple checkbox adding. + if (!info_tab.get('zebraStriping')) { + info_tab.add({ + type: 'checkbox', + label: event.editor.config.zebra_striping__checkboxLabel, + id: 'zebraStriping', + labelStyle: 'display: inline;', + requiredContent: 'table[' + zebra_attribute + ']', + setup: function (selectedTable) { + this.setValue(selectedTable.getAttribute(zebra_attribute)); + }, + commit: function (data, selectedTable) { + if (this.getValue()) { + selectedTable.setAttribute(zebra_attribute, true); + } else { + selectedTable.removeAttribute(zebra_attribute); + } + } + }); + } + }); + } + }); + +})(jQuery, CKEDITOR); diff --git a/modules/oe_theme_helper/src/Plugin/CKEditorPlugin/TableSort.php b/modules/oe_theme_helper/src/Plugin/CKEditorPlugin/TableSort.php new file mode 100755 index 000000000..c9b9e2703 --- /dev/null +++ b/modules/oe_theme_helper/src/Plugin/CKEditorPlugin/TableSort.php @@ -0,0 +1,70 @@ +getModulePath('oe_theme_helper') . '/js/table_sort.js'; + } + + /** + * {@inheritdoc} + */ + public function getConfig(Editor $editor) { + return []; + } + + /** + * {@inheritdoc} + */ + public function getButtons() { + return []; + } + + /** + * {@inheritdoc} + */ + public function isEnabled(Editor $editor) { + if (!$editor->hasAssociatedFilterFormat()) { + return FALSE; + } + + // "Sortable" option should be available only when the ECL table + // filter is enabled and the table button is present in the WYSIWYG toolbar. + $enabled = FALSE; + $format = $editor->getFilterFormat(); + if ($format->filters('filter_ecl_table')->status) { + $settings = $editor->getSettings(); + foreach ($settings['toolbar']['rows'] as $row) { + foreach ($row as $group) { + foreach ($group['items'] as $button) { + if ($button === 'Table') { + $enabled = TRUE; + } + } + } + } + } + + return $enabled; + } + +} diff --git a/modules/oe_theme_helper/src/Plugin/CKEditorPlugin/TableZebraStriping.php b/modules/oe_theme_helper/src/Plugin/CKEditorPlugin/TableZebraStriping.php new file mode 100755 index 000000000..c2eaf522b --- /dev/null +++ b/modules/oe_theme_helper/src/Plugin/CKEditorPlugin/TableZebraStriping.php @@ -0,0 +1,72 @@ +getModulePath('oe_theme_helper') . '/js/table_zebra_striping.js'; + } + + /** + * {@inheritdoc} + */ + public function getConfig(Editor $editor) { + return [ + 'zebra_striping__checkboxLabel' => $this->t('Zebra striping'), + ]; + } + + /** + * {@inheritdoc} + */ + public function getButtons() { + return []; + } + + /** + * {@inheritdoc} + */ + public function isEnabled(Editor $editor) { + if (!$editor->hasAssociatedFilterFormat()) { + return FALSE; + } + + // "Zebra striping" option should be available only when the ECL table + // filter is enabled and the table button is present in the WYSIWYG toolbar. + $enabled = FALSE; + $format = $editor->getFilterFormat(); + if ($format->filters('filter_ecl_table')->status) { + $settings = $editor->getSettings(); + foreach ($settings['toolbar']['rows'] as $row) { + foreach ($row as $group) { + foreach ($group['items'] as $button) { + if ($button === 'Table') { + $enabled = TRUE; + } + } + } + } + } + + return $enabled; + } + +} diff --git a/modules/oe_theme_helper/src/Plugin/Filter/FilterEclTable.php b/modules/oe_theme_helper/src/Plugin/Filter/FilterEclTable.php index 472dbe9aa..2f012d6ca 100644 --- a/modules/oe_theme_helper/src/Plugin/Filter/FilterEclTable.php +++ b/modules/oe_theme_helper/src/Plugin/Filter/FilterEclTable.php @@ -24,6 +24,7 @@ class FilterEclTable extends FilterBase { * {@inheritdoc} * * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function process($text, $langcode) { $result = new FilterProcessResult($text); @@ -36,12 +37,40 @@ public function process($text, $langcode) { $dom = Html::load($text); $xpath = new \DOMXPath($dom); - foreach ($xpath->query('//table[.//th]') as $table) { - // Skip the table if any cell spans over multiple columns or rows. - $span_cells = $xpath->query('.//*[self::th or self::td][(@colspan and @colspan > 1) or (@rowspan and @rowspan > 1)]', $table); - if ($span_cells->count() !== 0) { + foreach ($xpath->query('//table') as $table) { + // Put ECL related classes for table tag. + $this->elementAddClass($table, 'ecl-table'); + // Add classes related to "Zebra striping". + if ($table->getAttribute('data-striped') === 'true') { + $this->elementAddClass($table, 'ecl-table--zebra'); + $table->removeAttribute('data-striped'); + } + + // Put ECL related classes for thead tag. + foreach ($xpath->query('./thead', $table) as $thead) { + $this->elementAddClass($thead, 'ecl-table__head'); + } + + // Put ECL related classes for tbody tag. + foreach ($xpath->query('./tbody', $table) as $tbody) { + $this->elementAddClass($tbody, 'ecl-table__body'); + } + + // Put ECL related classes for tr tags. + foreach ($xpath->query('.//tr', $table) as $trow) { + $this->elementAddClass($trow, 'ecl-table__row'); + } + + // Skip further processing of the table if table without headers. + $ths = $xpath->query('.//th', $table); + if ($ths->count() === 0) { continue; } + else { + foreach ($xpath->query('.//thead/tr[1]/th', $table) as $th) { + $this->elementAddClass($th, 'ecl-table__header'); + } + } // Do not process tables that use th cells anywhere but in the first // column. @@ -56,19 +85,33 @@ public function process($text, $langcode) { $has_header_row = $xpath->query('(./thead/tr[1])[count(./*[not(self::th)]) = 0]', $table); if ($has_header_row->count()) { $header_row = $has_header_row[0]; - foreach ($xpath->query('./th', $header_row) as $cell) { - $headers[] = $cell->nodeValue; + foreach ($xpath->query('./th', $header_row) as $thead_cell) { + $this->elementAddClass($thead_cell, 'ecl-table__header'); + // Add data attribute related to "Sort". + if ($thead_cell->getAttribute('data-sortable') === 'true') { + $thead_cell->setAttribute('data-ecl-table-sort-toggle', ''); + $thead_cell->removeAttribute('data-sortable'); + + // Add additional attributes to enable sorting for table. + $table->setAttribute('data-ecl-table', ''); + $table->setAttribute('data-ecl-auto-init', 'Table'); + } + $headers[] = $thead_cell->nodeValue; } } + // Skip further processing of the table if any cell spans + // over multiple columns or rows. + $span_cells = $xpath->query('.//*[self::th or self::td][(@colspan and @colspan > 1) or (@rowspan and @rowspan > 1)]', $table); + if ($span_cells->count() !== 0) { + continue; + } + // Loop through all the table rows, aside from header ones. foreach ($xpath->query('.//tr[not(parent::thead)]', $table) as $row) { // Fetch all the cells inside the row. foreach ($xpath->query('./*[self::th or self::td]', $row) as $cell_index => $cell) { - $existing_class = $cell->getAttribute('class'); - $new_class = $existing_class ? "$existing_class ecl-table__cell" : 'ecl-table__cell'; - $cell->setAttribute('class', $new_class); - + $this->elementAddClass($cell, 'ecl-table__cell'); if (array_key_exists($cell_index, $headers)) { $cell->setAttribute('data-ecl-table-header', $headers[$cell_index]); } @@ -81,4 +124,20 @@ public function process($text, $langcode) { return $result; } + /** + * Adds class to element. + * + * @param \DOMNode $element + * Element. + * @param string $class + * Class that should be added. + */ + public function elementAddClass(\DOMNode $element, string $class): void { + $classes = $element->getAttribute('class'); + $classes_array = array_filter(array_map('trim', explode(' ', $classes))); + $classes_array[] = $class; + $classes = implode(' ', array_unique($classes_array)); + $element->setAttribute('class', $classes); + } + } diff --git a/modules/oe_theme_helper/tests/src/FunctionalJavascript/WysiwygTableTest.php b/modules/oe_theme_helper/tests/src/FunctionalJavascript/WysiwygTableTest.php new file mode 100644 index 000000000..2df2999a8 --- /dev/null +++ b/modules/oe_theme_helper/tests/src/FunctionalJavascript/WysiwygTableTest.php @@ -0,0 +1,379 @@ +container->get('theme_installer')->install(['oe_theme', 'seven']); + $this->config('system.theme')->set('default', 'oe_theme')->save(); + + // Rebuild the ui_pattern definitions to collect the ones provided by + // oe_theme itself. + \Drupal::service('plugin.manager.ui_patterns')->clearCachedDefinitions(); + + // Create a text format and associate this with CKEditor. + FilterFormat::create([ + 'format' => 'full_html', + 'name' => 'Full HTML', + 'weight' => 1, + 'roles' => [RoleInterface::AUTHENTICATED_ID], + 'filters' => [ + 'filter_ecl_table' => [ + 'status' => 1, + ], + ], + ])->save(); + + Editor::create([ + 'format' => 'full_html', + 'editor' => 'ckeditor', + 'settings' => [ + 'toolbar' => [ + 'rows' => [ + 0 => [ + 0 => [ + 'name' => 'Group with table', + 'items' => [ + 'Table', + 'Source', + ], + ], + ], + ], + ], + ], + ])->save(); + + $this->webUser = $this->drupalCreateUser([ + 'access administration pages', + 'create oe_page content', + 'edit own oe_page content', + 'view the administration theme', + ]); + } + + /** + * Test table widget in WYSIWYG. + */ + public function testWysiwygTable(): void { + $web_assert = $this->assertSession(); + $page = $this->getSession()->getPage(); + $this->drupalLogin($this->webUser); + // "Zebra striping" checkbox should be visible in Table properties dialog. + $this->drupalGet('node/add/oe_page'); + $this->waitForEditor(); + $this->waitOnCkeditorInstance('edit-body-0-value'); + $this->pressEditorButton('table'); + $this->assertNotEmpty($web_assert->waitForElement('css', '.cke_editor_edit-body-0-value_dialog')); + $web_assert->elementContains('css', '.cke_editor_edit-body-0-value_dialog .cke_dialog_ui_checkbox', 'Zebra striping'); + // "Zebra striping" checkbox should not be visible in Table properties + // dialog with disabled ECL table filter. + FilterFormat::load('full_html') + ->setFilterConfig('filter_ecl_table', ['status' => FALSE]) + ->save(); + $this->getSession()->reload(); + $this->waitForEditor(); + $this->waitOnCkeditorInstance('edit-body-0-value'); + $this->pressEditorButton('table'); + $this->assertNotEmpty($web_assert->waitForElement('css', '.cke_editor_edit-body-0-value_dialog')); + $web_assert->elementNotExists('css', '.cke_editor_edit-body-0-value_dialog .cke_dialog_ui_checkbox'); + // "Zebra striping" checkbox should be visible in Table properties dialog + // after enabling ECL table filter. + FilterFormat::load('full_html') + ->setFilterConfig('filter_ecl_table', ['status' => TRUE]) + ->save(); + $this->getSession()->reload(); + $this->waitForEditor(); + $this->waitOnCkeditorInstance('edit-body-0-value'); + $this->pressEditorButton('table'); + $this->assertNotEmpty($web_assert->waitForElement('css', '.cke_editor_edit-body-0-value_dialog')); + $this->click('.cke_editor_edit-body-0-value_dialog .cke_dialog_ui_checkbox'); + $page->pressButton('OK'); + // Data attribute should be present after enabling "Zebra striping" option. + $this->pressEditorButton('source'); + $this->assertStringContainsString('find('css', 'textarea.cke_source') + ->getValue()); + $this->pressEditorButton('source'); + // Assert enabled "Zebra striping" checkbox in Table properties dialog. + $this->assignNameToCkeditorIframe('edit-body-0-value-instance-id'); + $this->getSession()->switchToIFrame('edit-body-0-value-instance-id'); + $page->find('css', 'table > tbody > tr > td')->rightClick(); + $this->getSession()->switchToIFrame(); + $this->getSession()->switchToIFrame($page->find('css', '.cke_menu_panel iframe')->getAttribute('id')); + $this->clickLink('Table Properties'); + $this->getSession()->switchToIFrame(); + $this->assertNotEmpty($web_assert->waitForElement('css', '.cke_editor_edit-body-0-value_dialog')); + $this->assertTrue($page->findField('Zebra striping')->isChecked()); + // Enable first row headers for table. + $this->getOpenedDialogElement()->selectFieldOption('Headers', 'First Row'); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + // Put content for table headers. + $javascript = << thead > tr > th'); + header_cols.getItem(0).setHtml('Header text 1'); + header_cols.getItem(1).setHtml('Header text 2'); +})() +JS; + $this->getSession()->evaluateScript($javascript); + // Assert presence of classes in table related to "Zebra striping" option. + $page->fillField('Page title', 'test'); + $this->click('#cke_edit-oe-teaser-0-value .cke_button__source'); + $page->find('css', 'textarea.cke_source')->setValue('test'); + $page->fillField('Content owner (value 1)', 'Audit Board of the European Communities (http://publications.europa.eu/resource/authority/corporate-body/ABEC)'); + $page->pressButton('Save'); + $web_assert->elementExists('css', 'article .ecl table.ecl-table.ecl-table--zebra'); + // Assert absence of classes in table related to "Zebra striping" with + // disabled option. + $this->drupalGet('node/1/edit'); + $this->waitForEditor(); + $this->assignNameToCkeditorIframe('edit-body-0-value-instance-id'); + $this->getSession()->switchToIFrame('edit-body-0-value-instance-id'); + $page->find('css', 'table > tbody > tr > td')->rightClick(); + $this->getSession()->switchToIFrame(); + $this->getSession()->switchToIFrame($page->find('css', '.cke_menu_panel iframe')->getAttribute('id')); + $this->clickLink('Table Properties'); + $this->getSession()->switchToIFrame(); + $this->assertNotEmpty($web_assert->waitForElement('css', '.cke_editor_edit-body-0-value_dialog')); + $this->click('.cke_editor_edit-body-0-value_dialog .cke_dialog_ui_checkbox'); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + $page->pressButton('Save'); + $web_assert->elementNotExists('css', 'article .ecl table.ecl-table.ecl-table--zebra'); + $web_assert->elementExists('css', 'article .ecl table'); + + // If the filter is not enabled in an editor, + // the "Sort" plugin is not active. + FilterFormat::load('full_html') + ->setFilterConfig('filter_ecl_table', ['status' => FALSE]) + ->save(); + // Sortable field select box is not available as + // "ECL Table" plugin is disabled. + $this->drupalGet('node/1/edit'); + $this->waitForEditor(); + $this->assignNameToCkeditorIframe('edit-body-0-value-instance-id'); + $this->openCellPropertiesDialog('table > thead > tr > th'); + $this->assertFalse($page->hasField('Sortable')); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + + // If the filter is enabled in an editor, + // the "Sort" plugin is active. + FilterFormat::load('full_html') + ->setFilterConfig('filter_ecl_table', ['status' => TRUE]) + ->save(); + $this->getSession()->reload(); + $this->waitForEditor(); + $this->assignNameToCkeditorIframe('edit-body-0-value-instance-id'); + // The select is shown when right clicking a
. + $this->openCellPropertiesDialog('table > thead > tr > th'); + $this->assertFalse((bool) $this->getOpenedDialogElement()->findField('Sortable')->getAttribute('disabled')); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + + // The select is disabled when right clicking a . + $this->openCellPropertiesDialog('table > tbody > tr > td'); + $this->assertTrue((bool) $this->getOpenedDialogElement()->findField('Sortable')->getAttribute('disabled')); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + + // The select is shown when right clicking after selecting multiple . + $this->selectElementBySelector('table > thead > tr'); + $this->openCellPropertiesDialog('table > thead > tr > th'); + $this->assertFalse((bool) $this->getOpenedDialogElement()->findField('Sortable')->getAttribute('disabled')); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + + // The select is disabled when right clicking + // after selecting a mix of and . + $this->selectElementBySelector('table'); + $this->openCellPropertiesDialog('table > thead > tr > th'); + $this->assertTrue((bool) $this->getOpenedDialogElement()->findField('Sortable')->getAttribute('disabled')); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + + // Make sortable particular header column. + $this->selectElementBySelector('table > thead > tr > th'); + $this->openCellPropertiesDialog('table > thead > tr > th'); + $this->getOpenedDialogElement()->selectFieldOption('Sortable', 'yes'); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + // Assert correct saving of header column sort state. + $this->selectElementBySelector('table > thead > tr > th'); + $this->openCellPropertiesDialog('table > thead > tr > th'); + $this->assertEquals('yes', $this->getOpenedDialogElement()->findField('Sortable')->getValue()); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + + // Make not sortable particular header column. + $this->selectElementBySelector('table > thead > tr > th'); + $this->openCellPropertiesDialog('table > thead > tr > th'); + $this->getOpenedDialogElement()->selectFieldOption('Sortable', 'no'); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + // Assert correct saving of header column sort state. + $this->selectElementBySelector('table > thead > tr > th'); + $this->openCellPropertiesDialog('table > thead > tr > th'); + $this->assertEquals('no', $this->getOpenedDialogElement()->findField('Sortable')->getValue()); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + + // The select has no selection when the selection contains a mix of + // with and without the attribute. + $this->selectElementBySelector('table > thead > tr > th'); + $this->openCellPropertiesDialog('table > thead > tr > th'); + $this->getOpenedDialogElement()->selectFieldOption('Sortable', 'yes'); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + $this->selectElementBySelector('table > thead > tr'); + $this->openCellPropertiesDialog('table > thead > tr > th'); + $this->assertTrue(empty($this->getOpenedDialogElement()->findField('Sortable')->getValue())); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + // The attributes are untouched when if the dialog is submitted with the + // "no selection" option at the step above. + $this->selectElementBySelector('table > thead > tr > th'); + $this->openCellPropertiesDialog('table > thead > tr > th'); + $this->assertEquals('yes', $this->getOpenedDialogElement()->findField('Sortable')->getValue()); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + + // The attribute is added/removed on single/multiple selections + // based on the chosen value. + $this->selectElementBySelector('table > thead > tr'); + $this->openCellPropertiesDialog('table > thead > tr > th'); + $this->getOpenedDialogElement()->selectFieldOption('Sortable', 'no'); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + $this->selectElementBySelector('table > thead > tr'); + $this->openCellPropertiesDialog('table > thead > tr > th'); + $this->assertEquals('no', $this->getOpenedDialogElement()->findField('Sortable')->getValue()); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + + // Make sure that the selected column's sortability is correctly + // reflected on frontend. + $this->selectElementBySelector('table > thead > tr'); + $this->openCellPropertiesDialog('table > thead > tr > th'); + $this->getOpenedDialogElement()->selectFieldOption('Sortable', 'yes'); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + $this->selectElementBySelector('table > thead > tr'); + $this->openCellPropertiesDialog('table > thead > tr > th'); + $this->assertEquals('yes', $this->getOpenedDialogElement()->findField('Sortable')->getValue()); + $this->getOpenedDialogElement()->getParent()->getParent()->pressButton('OK'); + + $page->pressButton('Save'); + $headers = $this->getSession()->getPage()->findAll('css', 'table thead tr th[data-ecl-table-sort-toggle]'); + $this->assertCount(2, $headers); + } + + /** + * Open Cell Property dialog based on provided selector for a table cell. + * + * @param string $right_click_selector + * Right click selector. + */ + protected function openCellPropertiesDialog(string $right_click_selector): void { + $web_assert = $this->assertSession(); + $page = $this->getSession()->getPage(); + $this->getSession()->switchToIFrame('edit-body-0-value-instance-id'); + $page->find('css', $right_click_selector)->rightClick(); + $this->getSession()->switchToIFrame(); + $web_assert->waitForElementVisible('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' cke_menu_panel ')]"); + $this->getSession()->switchToIFrame($page->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' cke_menu_panel ') and not(@hidden)]/iframe")->getAttribute('id')); + $this->clickLink('Cell'); + $this->getSession()->switchToIFrame(); + // @todo Fix in EWPP-2734 the way for waiting for the showing of the context submenu. + $web_assert->waitForElementVisible('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' cke_menu_panel ')][last()]/iframe"); + $this->getSession()->wait(1000); + $this->getSession()->switchToIFrame($page->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' cke_menu_panel ') and not(@hidden)][last()]/iframe")->getAttribute('id')); + $this->clickLink('Cell Properties'); + $this->getSession()->switchToIFrame(); + $web_assert->waitForElementVisible('css', '#' . $this->getOpenedDialogElement()->getAttribute('id')); + } + + /** + * Get current dialog element related to the WYSIWYG. + */ + protected function getOpenedDialogElement(): ?NodeElement { + $javascript = <<getSession()->evaluateScript($javascript); + return $this->getSession()->getPage()->findById($dialogId); + } + + /** + * Make selection inside the WYSIWYG content. + * + * @param string $selector + * Selector of element. + * @param string $instance_id + * (optional) The CKEditor instance ID. Defaults to 'edit-body-0-value'. + */ + protected function selectElementBySelector(string $selector, string $instance_id = 'edit-body-0-value'): void { + $javascript = <<getSession()->evaluateScript($javascript); + } + + /** + * Wait for a CKEditor instance to finish loading and initializing. + * + * @param string $instance_id + * The CKEditor instance ID. + * @param int $timeout + * (optional) Timeout in milliseconds, defaults to 10000. + */ + protected function waitOnCkeditorInstance($instance_id, $timeout = 10000) { + $condition = <<getSession()->wait($timeout, $condition); + } + +} diff --git a/modules/oe_theme_helper/tests/src/Unit/FilterEclTableTest.php b/modules/oe_theme_helper/tests/src/Unit/FilterEclTableTest.php index 9e7bbf434..34291877d 100644 --- a/modules/oe_theme_helper/tests/src/Unit/FilterEclTableTest.php +++ b/modules/oe_theme_helper/tests/src/Unit/FilterEclTableTest.php @@ -45,55 +45,67 @@ public function processDataProvider(): array { return [ 'Full table with thead and tfoot' => [ '

Some text before table

Caption
Column 1Column 2
1-11-2
2-12-2
Footer 1Footer 2

Some text after table

', - '

Some text before table

Caption
Column 1Column 2
1-11-2
2-12-2
Footer 1Footer 2

Some text after table

', + '

Some text before table

Caption
Column 1Column 2
1-11-2
2-12-2
Footer 1Footer 2

Some text after table

', + ], + 'Table with zebra striping' => [ + '

Some text before table

Caption
Column 1Column 2
1-11-2
2-12-2
Footer 1Footer 2

Some text after table

', + '

Some text before table

Caption
Column 1Column 2
1-11-2
2-12-2
Footer 1Footer 2

Some text after table

', + ], + 'Table with zebra striping and sort' => [ + '

Some text before table

Caption
Column 1Column 2
1-11-2
2-12-2
Footer 1Footer 2

Some text after table

', + '

Some text before table

Caption
Column 1Column 2
1-11-2
2-12-2
Footer 1Footer 2

Some text after table

', + ], + 'Table with sort' => [ + '

Some text before table

Caption
Column 1Column 2
1-11-2
2-12-2
Footer 1Footer 2

Some text after table

', + '

Some text before table

Caption
Column 1Column 2
1-11-2
2-12-2
Footer 1Footer 2

Some text after table

', ], 'Table with vertical header - only class is added' => [ '
Row 11-11-2
Row 22-12-2
Row 33-13-2
', - '
Row 11-11-2
Row 22-12-2
Row 33-13-2
', + '
Row 11-11-2
Row 22-12-2
Row 33-13-2
', ], 'Table with horizontal and vertical headers' => [ '
Column 1Column 2Column 3
Row 11-21-3
Row 22-22-3
', - '
Column 1Column 2Column 3
Row 11-21-3
Row 22-22-3
', + '
Column 1Column 2Column 3
Row 11-21-3
Row 22-22-3
', ], 'Table without tbody' => [ '
Caption
Column 1Column 2
1-11-2
2-12-2
', - '
Caption
Column 1Column 2
1-11-2
2-12-2
', + '
Caption
Column 1Column 2
1-11-2
2-12-2
', ], 'Multiple tables' => [ '
Column A1Column A2
A1-1A1-2
A2-1A2-2
B1-1B1-2
B2-1B2-2
Column C1Column C2Column C3
C1-1C1-2C1-3
C2-1C2-2C2-3
', - '
Column A1Column A2
A1-1A1-2
A2-1A2-2
B1-1B1-2
B2-1B2-2
Column C1Column C2Column C3
C1-1C1-2C1-3
C2-1C2-2C2-3
', + '
Column A1Column A2
A1-1A1-2
A2-1A2-2
B1-1B1-2
B2-1B2-2
Column C1Column C2Column C3
C1-1C1-2C1-3
C2-1C2-2C2-3
', ], - 'Table without any th - not processed' => [ - '
1-11-2
2-12-2
3-13-2
', + 'Table without any th - only general classes added' => [ '
1-11-2
2-12-2
3-13-2
', + '
1-11-2
2-12-2
3-13-2
', ], - 'Table with cells spanning multiple rows - not processed' => [ - '
Column 1Column 2Column 3
1-11-21-3
2-22-3
', + 'Table with cells spanning multiple rows - only general classes added' => [ '
Column 1Column 2Column 3
1-11-21-3
2-22-3
', + '
Column 1Column 2Column 3
1-11-21-3
2-22-3
', ], - 'Table with cells spanning multiple columns - not processed' => [ - '
Column 1Column 3
1-11-21-
2-12-22-3
', + 'Table with cells spanning multiple columns - only general classes added' => [ '
Column 1Column 3
1-11-21-
2-12-22-3
', + '
Column 1Column 3
1-11-21-
2-12-22-3
', ], - 'Table with header cells spanning multiple rows - not processed' => [ - '
Column 1Column 2
Column 4
1-11-2
2-12-2
', + 'Table with header cells spanning multiple rows - only general classes added' => [ '
Column 1Column 2
Column 4
1-11-2
2-12-2
', + '
Column 1Column 2
Column 4
1-11-2
2-12-2
', ], - 'Table with header cells spanning multiple columns - not processed' => [ - '
Column 1Column 2Column 3
1-11-3
2-12-22-3
', + 'Table with header cells spanning multiple columns - only general classes added' => [ '
Column 1Column 2Column 3
1-11-3
2-12-22-3
', + '
Column 1Column 2Column 3
1-11-3
2-12-22-3
', ], - 'Table with th cell in body - not processed' => [ - '
Column 1Column 2
1-11-2
2-12-2
', + 'Table with th cell in body - only general classes added' => [ '
Column 1Column 2
1-11-2
2-12-2
', + '
Column 1Column 2
1-11-2
2-12-2
', ], 'Table with multiple header rows - first row is used' => [ '
Column 1Column 2Column 3
Column 4Column 5Column 6
1-11-21-3
', - '
Column 1Column 2Column 3
Column 4Column 5Column 6
1-11-21-3
', + '
Column 1Column 2Column 3
Column 4Column 5Column 6
1-11-21-3
', ], 'Table header with invalid header - only class is added' => [ '
Column 1Column 2
1-11-2
', - '
Column 1Column 2
1-11-2
', + '
Column 1Column 2
1-11-2
', ], 'Table wrapped in HTML comment - not processed' => [ '', diff --git a/patches/@ecl/twig-component-site-footer+3.4.1.patch b/patches/@ecl/twig-component-site-footer+3.4.2.patch similarity index 100% rename from patches/@ecl/twig-component-site-footer+3.4.1.patch rename to patches/@ecl/twig-component-site-footer+3.4.2.patch diff --git a/patches/@ecl/twig-component-table+3.4.1.patch b/patches/@ecl/twig-component-table+3.4.2.patch similarity index 100% rename from patches/@ecl/twig-component-table+3.4.1.patch rename to patches/@ecl/twig-component-table+3.4.2.patch diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 580fdc812..77f5faceb 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -7,7 +7,7 @@ - +