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 CaptionColumn 1 | Column 2 |
---|
1-1 | 1-2 | 2-1 | 2-2 | Footer 1 | Footer 2 | Some text after table ',
- 'Some text before table CaptionColumn 1 | Column 2 |
---|
1-1 | 1-2 | 2-1 | 2-2 | Footer 1 | Footer 2 | Some text after table ',
+ 'Some text before table Caption|
1-1 | 1-2 | 2-1 | 2-2 | Footer 1 | Footer 2 | Some text after table ',
+ ],
+ 'Table with zebra striping' => [
+ 'Some text before table CaptionColumn 1 | Column 2 |
---|
1-1 | 1-2 | 2-1 | 2-2 | Footer 1 | Footer 2 | Some text after table ',
+ 'Some text before table Caption|
1-1 | 1-2 | 2-1 | 2-2 | Footer 1 | Footer 2 | Some text after table ',
+ ],
+ 'Table with zebra striping and sort' => [
+ 'Some text before table CaptionColumn 1 | Column 2 |
---|
1-1 | 1-2 | 2-1 | 2-2 | Footer 1 | Footer 2 | Some text after table ',
+ 'Some text before table Caption|
1-1 | 1-2 | 2-1 | 2-2 | Footer 1 | Footer 2 | Some text after table ',
+ ],
+ 'Table with sort' => [
+ 'Some text before table CaptionColumn 1 | Column 2 |
---|
1-1 | 1-2 | 2-1 | 2-2 | Footer 1 | Footer 2 | Some text after table ',
+ 'Some text before table Caption|
1-1 | 1-2 | 2-1 | 2-2 | Footer 1 | Footer 2 | Some text after table ',
],
'Table with vertical header - only class is added' => [
'Row 1 | 1-1 | 1-2 |
---|
Row 2 | 2-1 | 2-2 |
---|
Row 3 | 3-1 | 3-2 |
---|
',
- 'Row 1 | 1-1 | 1-2 |
---|
Row 2 | 2-1 | 2-2 |
---|
Row 3 | 3-1 | 3-2 |
---|
',
+ 'Row 1 | 1-1 | 1-2 |
---|
Row 2 | 2-1 | 2-2 |
---|
Row 3 | 3-1 | 3-2 |
---|
',
],
'Table with horizontal and vertical headers' => [
'Column 1 | Column 2 | Column 3 |
---|
Row 1 | 1-2 | 1-3 |
---|
Row 2 | 2-2 | 2-3 |
---|
',
- 'Column 1 | Column 2 | Column 3 |
---|
Row 1 | 1-2 | 1-3 |
---|
Row 2 | 2-2 | 2-3 |
---|
',
+ '',
],
'Table without tbody' => [
'CaptionColumn 1 | Column 2 |
---|
1-1 | 1-2 | 2-1 | 2-2 | ',
- 'CaptionColumn 1 | Column 2 |
---|
1-1 | 1-2 | 2-1 | 2-2 | ',
+ '',
],
'Multiple tables' => [
'Column A1 | Column A2 |
---|
A1-1 | A1-2 | A2-1 | A2-2 |
Column C1 | Column C2 | Column C3 |
---|
C1-1 | C1-2 | C1-3 | C2-1 | C2-2 | C2-3 | ',
- 'Column A1 | Column A2 |
---|
A1-1 | A1-2 | A2-1 | A2-2 |
Column C1 | Column C2 | Column C3 |
---|
C1-1 | C1-2 | C1-3 | C2-1 | C2-2 | C2-3 | ',
+ '',
],
- 'Table without any th - not processed' => [
- '',
+ 'Table without any th - only general classes added' => [
'',
+ '',
],
- 'Table with cells spanning multiple rows - not processed' => [
- 'Column 1 | Column 2 | Column 3 |
---|
1-1 | 1-2 | 1-3 | 2-2 | 2-3 | ',
+ 'Table with cells spanning multiple rows - only general classes added' => [
'Column 1 | Column 2 | Column 3 |
---|
1-1 | 1-2 | 1-3 | 2-2 | 2-3 | ',
+ '',
],
- 'Table with cells spanning multiple columns - not processed' => [
- 'Column 1 | Column 3 |
---|
1-1 | 1-2 | 1- | 2-1 | 2-2 | 2-3 | ',
+ 'Table with cells spanning multiple columns - only general classes added' => [
'Column 1 | Column 3 |
---|
1-1 | 1-2 | 1- | 2-1 | 2-2 | 2-3 | ',
+ '',
],
- 'Table with header cells spanning multiple rows - not processed' => [
- 'Column 1 | Column 2 |
---|
Column 4 |
---|
1-1 | 1-2 | 2-1 | 2-2 | ',
+ 'Table with header cells spanning multiple rows - only general classes added' => [
'Column 1 | Column 2 |
---|
Column 4 |
---|
1-1 | 1-2 | 2-1 | 2-2 | ',
+ '',
],
- 'Table with header cells spanning multiple columns - not processed' => [
- 'Column 1 | Column 2 | Column 3 |
---|
1-1 | 1-3 | 2-1 | 2-2 | 2-3 | ',
+ 'Table with header cells spanning multiple columns - only general classes added' => [
'Column 1 | Column 2 | Column 3 |
---|
1-1 | 1-3 | 2-1 | 2-2 | 2-3 | ',
+ '',
],
- 'Table with th cell in body - not processed' => [
- 'Column 1 | Column 2 |
---|
1-1 | 1-2 |
---|
2-1 | 2-2 | ',
+ 'Table with th cell in body - only general classes added' => [
'Column 1 | Column 2 |
---|
1-1 | 1-2 |
---|
2-1 | 2-2 | ',
+ '',
],
'Table with multiple header rows - first row is used' => [
'Column 1 | Column 2 | Column 3 |
---|
Column 4 | Column 5 | Column 6 |
---|
1-1 | 1-2 | 1-3 | ',
- 'Column 1 | Column 2 | Column 3 |
---|
Column 4 | Column 5 | Column 6 |
---|
1-1 | 1-2 | 1-3 | ',
+ '|
Column 4 | Column 5 | Column 6 |
---|
1-1 | 1-2 | 1-3 | ',
],
'Table header with invalid header - only class is added' => [
'',
- '',
+ '',
],
'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 @@
-
+
|