Unverified Commit a373aa3d authored by lauriii's avatar lauriii

Issue #3083051 by bnjmnm, anushrikumari, lauriii, JeroenT, nod_, ac, alexpott:...

Issue #3083051 by bnjmnm, anushrikumari, lauriii, JeroenT, nod_, ac, alexpott: Refactor tabledrag  when core issues are resolved
parent d9b41a88
<?php
namespace Drupal\tabledrag_test\Form;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a form for testing nested draggable tables.
*/
class NestedTableDragTestForm extends TableDragTestForm {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'nested_tabledrag_test_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$parent_row_ids = ['parent_1', 'parent_2', 'parent_3'];
$parent_rows = array_combine($parent_row_ids, $parent_row_ids);
$form['table'] = $this->buildTestTable($parent_rows, 'tabledrag-test-parent-table', 'tabledrag-test-nested-parent', FALSE);
$form['table']['#caption'] = $this->t('Parent table');
$form['table'][reset($parent_row_ids)]['title'] = $this->buildTestTable() + ['#caption' => $this->t('Nested table')];
$form['actions'] = $this->buildFormActions();
return $form;
}
}
......@@ -44,46 +44,68 @@ public function getFormId() {
}
/**
* {@inheritdoc}
* Builds the draggable test table.
*
* @param array $rows
* (optional) Rows that should be shown on the table. Default value is an
* empty array.
* @param string $table_id
* (optional) An HTML ID for the table, defaults to 'tabledrag-test-table'.
* @param string $group_prefix
* (optional) A prefix for HTML classes generated in the method, defaults to
* 'tabledrag-test'.
* @param bool $indentation
* (optional) A boolean indicating whether the rows can be indented,
* defaults to TRUE.
*
* @return array
* The renderable array of the draggable table used for testing.
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['table'] = [
protected function buildTestTable(array $rows = [], $table_id = 'tabledrag-test-table', $group_prefix = 'tabledrag-test', $indentation = TRUE) {
$tabledrag = [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => "$group_prefix-weight",
],
];
if ($indentation) {
$tabledrag[] = [
'action' => 'match',
'relationship' => 'parent',
'group' => "$group_prefix-parent",
'subgroup' => "$group_prefix-parent",
'source' => "$group_prefix-id",
'hidden' => TRUE,
'limit' => 2,
];
$tabledrag[] = [
'action' => 'depth',
'relationship' => 'group',
'group' => "$group_prefix-depth",
'hidden' => TRUE,
];
}
$table = [
'#type' => 'table',
'#header' => [
[
'data' => $this->t('Text'),
'colspan' => 4,
'colspan' => $indentation ? 4 : 2,
],
$this->t('Weight'),
],
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'tabledrag-test-weight',
],
[
'action' => 'match',
'relationship' => 'parent',
'group' => 'tabledrag-test-parent',
'subgroup' => 'tabledrag-test-parent',
'source' => 'tabledrag-test-id',
'hidden' => TRUE,
'limit' => 2,
],
[
'action' => 'depth',
'relationship' => 'group',
'group' => 'tabledrag-test-depth',
'hidden' => TRUE,
],
],
'#attributes' => ['id' => 'tabledrag-test-table'],
'#tabledrag' => $tabledrag,
'#attributes' => ['id' => $table_id],
'#attached' => ['library' => ['tabledrag_test/tabledrag']],
];
// Provide a default set of five rows.
$rows = $this->state->get('tabledrag_test_table', array_flip(range(1, 5)));
$rows = !empty($rows) ? $rows :
$this->state->get('tabledrag_test_table', array_flip(range(1, 5)));
foreach ($rows as $id => $row) {
if (!is_array($row)) {
$row = [];
......@@ -101,43 +123,56 @@ public function buildForm(array $form, FormStateInterface $form_state) {
$row['classes'][] = 'draggable';
}
$form['table'][$id] = [
$table[$id] = [
'title' => [
'indentation' => [
'#theme' => 'indentation',
'#size' => $row['depth'],
'#size' => $indentation ? $row['depth'] : 0,
],
'#plain_text' => "Row with id $id",
],
'id' => [
'#type' => 'hidden',
'#value' => $id,
'#attributes' => ['class' => ['tabledrag-test-id']],
'#parents' => ['table', $id, 'id'],
'#attributes' => ['class' => ["$group_prefix-id"]],
],
'parent' => [
'#attributes' => ['class' => $row['classes']],
];
if ($indentation) {
$table[$id]['parent'] = [
'#type' => 'hidden',
'#default_value' => $row['parent'],
'#parents' => ['table', $id, 'parent'],
'#attributes' => ['class' => ['tabledrag-test-parent']],
],
'depth' => [
'#attributes' => ['class' => ["$group_prefix-parent"]],
];
$table[$id]['depth'] = [
'#type' => 'hidden',
'#default_value' => $row['depth'],
'#attributes' => ['class' => ['tabledrag-test-depth']],
],
'weight' => [
'#type' => 'weight',
'#default_value' => $row['weight'],
'#attributes' => ['class' => ['tabledrag-test-weight']],
],
'#attributes' => ['class' => $row['classes']],
'#parents' => ['table', $id, 'depth'],
'#attributes' => ['class' => ["$group_prefix-depth"]],
];
}
$table[$id]['weight'] = [
'#type' => 'weight',
'#default_value' => $row['weight'],
'#parents' => ['table', $id, 'weight'],
'#attributes' => ['class' => ["$group_prefix-weight"]],
];
}
$form['save'] = [
'#type' => 'submit',
'#value' => $this->t('Save'),
];
return $table;
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// Provide a default set of five rows.
$form['table'] = $this->buildTestTable();
$form['actions'] = $this->buildFormActions();
return $form;
}
......@@ -146,12 +181,44 @@ public function buildForm(array $form, FormStateInterface $form_state) {
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$test_table = [];
foreach ($form_state->getValue('table') as $row) {
$test_table[$row['id']] = $row;
$operation = isset($form_state->getTriggeringElement()['#op']) ?
$form_state->getTriggeringElement()['#op'] :
'save';
switch ($operation) {
case 'reset':
$this->state->set('tabledrag_test_table', array_flip(range(1, 5)));
break;
default:
$test_table = [];
foreach ($form_state->getValue('table') as $row) {
$test_table[$row['id']] = $row;
}
$this->state->set('tabledrag_test_table', $test_table);
break;
}
}
$this->state->set('tabledrag_test_table', $test_table);
/**
* Builds the test table form actions.
*
* @return array
* The renderable array of form actions.
*/
protected function buildFormActions() {
return [
'#type' => 'actions',
'save' => [
'#type' => 'submit',
'#value' => $this->t('Save'),
],
'reset' => [
'#type' => 'submit',
'#op' => 'reset',
'#value' => $this->t('Reset'),
],
];
}
}
......@@ -5,3 +5,11 @@ tabledrag_test.test_form:
_title: 'Draggable table test'
requirements:
_access: 'TRUE'
tabledrag_test.nested_tabledrag_test_form:
path: '/tabledrag_test_nested'
defaults:
_form: '\Drupal\tabledrag_test\Form\NestedTableDragTestForm'
_title: 'Nested draggable table test'
requirements:
_access: 'TRUE'
......@@ -35,6 +35,20 @@ class TableDragTest extends WebDriverTestBase {
*/
protected $state;
/**
* Xpath selector for finding tabledrag indentation elements in a table row.
*
* @var string
*/
protected static $indentationXpathSelector = 'child::td[1]/*[contains(concat(" ", normalize-space(@class), " "), " js-indentation ")][contains(concat(" ", normalize-space(@class), " "), " indentation ")]';
/**
* Xpath selector for finding the tabledrag changed marker.
*
* @var string
*/
protected static $tabledragChangedXpathSelector = 'child::td[1]/abbr[contains(concat(" ", normalize-space(@class), " "), " tabledrag-changed ")]';
/**
* {@inheritdoc}
*/
......@@ -130,16 +144,31 @@ public function testDragAndDrop() {
* Tests accessibility through keyboard of the tabledrag functionality.
*/
public function testKeyboardAccessibility() {
$this->state->set('tabledrag_test_table', array_flip(range(1, 5)));
$this->assertKeyboardAccessibility();
}
$expected_table = [
/**
* Asserts accessibility through keyboard of a test draggable table.
*
* @param string $drupal_path
* The drupal path where the '#tabledrag-test-table' test table is present.
* Defaults to 'tabledrag_test'.
* @param array|null $structure
* The expected table structure. If this isn't specified or equals NULL,
* then the expected structure will be set by this method. Defaults to NULL.
*/
protected function assertKeyboardAccessibility($drupal_path = 'tabledrag_test', $structure = NULL) {
$expected_table = $structure ?: [
['id' => 1, 'weight' => 0, 'parent' => '', 'indentation' => 0, 'changed' => FALSE],
['id' => 2, 'weight' => 0, 'parent' => '', 'indentation' => 0, 'changed' => FALSE],
['id' => 3, 'weight' => 0, 'parent' => '', 'indentation' => 0, 'changed' => FALSE],
['id' => 4, 'weight' => 0, 'parent' => '', 'indentation' => 0, 'changed' => FALSE],
['id' => 5, 'weight' => 0, 'parent' => '', 'indentation' => 0, 'changed' => FALSE],
];
$this->drupalGet('tabledrag_test');
if (!empty($drupal_path)) {
$this->state->set('tabledrag_test_table', array_flip(range(1, 5)));
$this->drupalGet($drupal_path);
}
$this->assertDraggableTable($expected_table);
// Nest the row with id 2 as child of row 1.
......@@ -307,6 +336,139 @@ protected function assertOrder(array $items) {
$this->assertSame($items, array_values($strings), "Strings found on the page but incorrectly ordered.");
}
/**
* Tests nested draggable tables through keyboard.
*/
public function testNestedDraggableTables() {
$this->state->set('tabledrag_test_table', array_flip(range(1, 5)));
$this->drupalGet('tabledrag_test_nested');
$this->assertKeyboardAccessibility('');
// Now move the rows of the parent table.
$expected_parent_table = [
[
'id' => 'parent_1',
'weight' => 0,
'parent' => '',
'indentation' => 0,
'changed' => FALSE,
],
[
'id' => 'parent_2',
'weight' => 0,
'parent' => '',
'indentation' => 0,
'changed' => FALSE,
],
[
'id' => 'parent_3',
'weight' => 0,
'parent' => '',
'indentation' => 0,
'changed' => FALSE,
],
];
$this->assertDraggableTable($expected_parent_table, 'tabledrag-test-parent-table', TRUE);
// Switch parent table rows children.
$this->moveRowWithKeyboard($this->findRowById('parent_2', 'tabledrag-test-parent-table'), 'up');
$expected_parent_table = [
[
'id' => 'parent_2',
'weight' => -10,
'parent' => '',
'indentation' => 0,
'changed' => TRUE,
],
[
'id' => 'parent_1',
'weight' => -9,
'parent' => '',
'indentation' => 0,
'changed' => FALSE,
],
[
'id' => 'parent_3',
'weight' => -8,
'parent' => '',
'indentation' => 0,
'changed' => FALSE,
],
];
$this->assertDraggableTable($expected_parent_table, 'tabledrag-test-parent-table', TRUE);
// Try to move the row that contains the nested table to the last position.
// Order should be changed, but changed marker isn't added.
// This seems to be buggy, but this is the original behavior.
$this->moveRowWithKeyboard($this->findRowById('parent_1', 'tabledrag-test-parent-table'), 'down');
$expected_parent_table = [
[
'id' => 'parent_2',
'weight' => -10,
'parent' => '',
'indentation' => 0,
'changed' => TRUE,
],
[
'id' => 'parent_3',
'weight' => -9,
'parent' => '',
'indentation' => 0,
'changed' => FALSE,
],
// Since 'parent_1' row was moved, it should be marked as changed, but
// this would fail with core tabledrag.js.
[
'id' => 'parent_1',
'weight' => -8,
'parent' => '',
'indentation' => 0,
'changed' => NULL,
],
];
$this->assertDraggableTable($expected_parent_table, 'tabledrag-test-parent-table', TRUE);
// Re-test the nested draggable table.
$expected_child_table_structure = [
[
'id' => 5,
'weight' => -10,
'parent' => '',
'indentation' => 0,
'changed' => FALSE,
],
[
'id' => 3,
'weight' => -10,
'parent' => 5,
'indentation' => 1,
'changed' => TRUE,
],
[
'id' => 1,
'weight' => -9,
'parent' => '',
'indentation' => 0,
'changed' => TRUE,
],
[
'id' => 2,
'weight' => -10,
'parent' => 1,
'indentation' => 1,
'changed' => TRUE,
],
[
'id' => 4,
'weight' => -10,
'parent' => 2,
'indentation' => 2,
'changed' => TRUE,
],
];
$this->assertDraggableTable($expected_child_table_structure);
}
/**
* Asserts the whole structure of the draggable test table.
*
......@@ -317,13 +479,18 @@ protected function assertOrder(array $items) {
* - parent: the expected parent ID for the row.
* - indentation: how many indents the row should have.
* - changed: whether or not the row should have been marked as changed.
* @param string $table_id
* The ID of the table. Defaults to 'tabledrag-test-table'.
* @param bool $skip_missing
* Whether assertions done on missing elements value may be skipped or not.
* Defaults to FALSE.
*/
protected function assertDraggableTable(array $structure) {
$rows = $this->getSession()->getPage()->findAll('xpath', '//table[@id="tabledrag-test-table"]/tbody/tr');
$this->assertSession()->elementsCount('xpath', '//table[@id="tabledrag-test-table"]/tbody/tr', count($structure));
protected function assertDraggableTable(array $structure, $table_id = 'tabledrag-test-table', $skip_missing = FALSE) {
$rows = $this->getSession()->getPage()->findAll('xpath', "//table[@id='$table_id']/tbody/tr");
$this->assertSession()->elementsCount('xpath', "//table[@id='$table_id']/tbody/tr", count($structure));
foreach ($structure as $delta => $expected) {
$this->assertTableRow($rows[$delta], $expected['id'], $expected['weight'], $expected['parent'], $expected['indentation'], $expected['changed']);
$this->assertTableRow($rows[$delta], $expected['id'], $expected['weight'], $expected['parent'], $expected['indentation'], $expected['changed'], $skip_missing);
}
}
......@@ -340,18 +507,30 @@ protected function assertDraggableTable(array $structure) {
* The expected parent ID.
* @param int $indentation
* The expected indentation of the row.
* @param bool $changed
* Whether or not the row should have been marked as changed.
* @param bool|null $changed
* Whether or not the row should have been marked as changed. NULL means
* that this assertion should be skipped.
* @param bool $skip_missing
* Whether assertions done on missing elements value may be skipped or not.
* Defaults to FALSE.
*/
protected function assertTableRow(NodeElement $row, $id, $weight, $parent = '', $indentation = 0, $changed = FALSE) {
protected function assertTableRow(NodeElement $row, $id, $weight, $parent = '', $indentation = 0, $changed = FALSE, $skip_missing = FALSE) {
// Assert that the row position is correct by checking that the id
// corresponds.
$this->assertSession()->hiddenFieldValueEquals("table[$id][id]", $id, $row);
$this->assertSession()->hiddenFieldValueEquals("table[$id][parent]", $parent, $row);
$id_name = "table[$id][id]";
if (!$skip_missing || $row->find('hidden_field_selector', ['hidden_field', $id_name])) {
$this->assertSession()->hiddenFieldValueEquals($id_name, $id, $row);
}
$parent_name = "table[$id][parent]";
if (!$skip_missing || $row->find('hidden_field_selector', ['hidden_field', $parent_name])) {
$this->assertSession()->hiddenFieldValueEquals($parent_name, $parent, $row);
}
$this->assertSession()->fieldValueEquals("table[$id][weight]", $weight, $row);
$this->assertSession()->elementsCount('css', '.js-indentation.indentation', $indentation, $row);
$this->assertSession()->elementsCount('xpath', static::$indentationXpathSelector, $indentation, $row);
// A row is marked as changed when the related markup is present.
$this->assertSession()->elementsCount('css', 'abbr.tabledrag-changed', (int) $changed, $row);
if ($changed !== NULL) {
$this->assertSession()->elementsCount('xpath', static::$tabledragChangedXpathSelector, (int) $changed, $row);
}
}
/**
......@@ -359,12 +538,14 @@ protected function assertTableRow(NodeElement $row, $id, $weight, $parent = '',
*
* @param string $id
* The ID of the row.
* @param string $table_id
* The ID of the parent table. Defaults to 'tabledrag-test-table'.
*
* @return \Behat\Mink\Element\NodeElement
* The row element.
*/
protected function findRowById($id) {
$xpath = "//table[@id='tabledrag-test-table']/tbody/tr[.//input[@name='table[$id][id]']]";
protected function findRowById($id, $table_id = 'tabledrag-test-table') {
$xpath = "//table[@id='$table_id']/tbody/tr[.//input[@name='table[$id][id]']]";
$row = $this->getSession()->getPage()->find('xpath', $xpath);
$this->assertNotEmpty($row);
return $row;
......
......@@ -46,7 +46,7 @@ public function testEntityForm() {
$this->drupalGet('entity_test/structure/entity_test/form-display');
$this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'content')->isSelected());
$this->getSession()->getPage()->clickLink('Show row weights');
$this->getSession()->getPage()->pressButton('Show row weights');
$this->assertSession()->waitForElementVisible('css', '[name="fields[field_test_text][region]"]');
$this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'hidden');
$this->assertSession()->assertWaitOnAjaxRequest();
......@@ -73,7 +73,7 @@ public function testEntityView() {
$this->drupalGet('entity_test/structure/entity_test/display');
$this->assertSession()->elementExists('css', '.region-content-message.region-empty');
$this->getSession()->getPage()->clickLink('Show row weights');
$this->getSession()->getPage()->pressButton('Show row weights');
$this->assertSession()->waitForElementVisible('css', '[name="fields[field_test_text][region]"]');
$this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'hidden')->isSelected());
......
......@@ -5,7 +5,7 @@
use Drupal\FunctionalJavascriptTests\TableDrag\TableDragTest;
/**
* Runs TableDragTest in Claro.
* Tests draggable tables with Claro theme.
*
* @group claro
*
......@@ -21,10 +21,19 @@ class ClaroTableDragTest extends TableDragTest {
/**
* {@inheritdoc}
*/
protected function findWeightsToggle($expected_text) {
$toggle = $this->getSession()->getPage()->findLink($expected_text);
$this->assertNotEmpty($toggle